diff --git a/.agents/benchmarks/restructure-attestation/README.md b/.agents/benchmarks/restructure-attestation/README.md new file mode 100644 index 00000000..8e85351f --- /dev/null +++ b/.agents/benchmarks/restructure-attestation/README.md @@ -0,0 +1,137 @@ +# Final Mission Attestation — VFS Right-Thing Restructure + +Machine-readable definition-of-done record tying the full honest gate and the +codex benchmark contract to the exact commit (VAL-GATE-012, VAL-CROSS-012). + +## Attested commit + +- Commit: `230c30a6f2e7184b0578eb2dec087b79c0f78078` (`dev`) +- Binary: `target/release/agentfs` built from that commit by `scripts/gate.sh` + (release workspace build; `rust-toolchain.toml` nightly) +- Runner: `Linux 7.1.2-3-cachyos x86_64 GNU/Linux`; toolchain + `cargo 1.97.0-nightly (4f9b52075 2026-05-01)` +- Attestation artifacts: `full-gate.json` (gate + shell suite + phase8 + canon + + pjdfstest + cross-surface flows), `medians.json` (benchmark contract), + `bench-milestones.json` (per-milestone BENCH record, VAL-GATE-015) + +## Full gate (all green) + +`scripts/gate.sh` at the attested commit: fmt, clippy `--workspace +--all-targets -D warnings`, `cargo test --workspace`, release build, strict +shell suite (`AGENTFS_GATE_STRICT=1 crates/agentfs-cli/tests/all.sh`, both +corruption-torture transport legs), `phase8-validation.py --smoke` (noopen / +flush / external-base-drift coherence harnesses run inside it), and +`scripts/validation/consistency-canon.sh`. Result: exit 0 — +`PASS=24 SKIP=0 FAIL=0`, phase8 `failed_gates: []`, canon all rules PASS. +Full detail: `full-gate.json`. + +pjdfstest: `scripts/validation/posix/run-pjdfstest.sh --profile phase5-ci +--agentfs-bin target/release/agentfs` — PASS (311 tests, run standalone at the +attested commit). + +## Benchmark contract + +Exact invocation (library/perf-contract.md, services.yaml `bench-multi`): + +```bash +python3 scripts/validation/git-workload-benchmark-multi.py \ + --label m7-opt-sandbox-bringup-repeat --iterations 5 --warmup 1 \ + --agentfs-bin "$PWD/target/release/agentfs" \ + --output --keep-iterations \ + --source .agents/benchmarks/fixtures/codex \ + --read-files 64 --read-bytes 4096 --edit-files 8 +``` + +Baseline: the ACTIVE post-M3 baseline +`.agents/benchmarks/restructure-baseline/medians.json` (recorded at +`2b811f0`, orchestrator-signed rebaseline; the archived M1 artifact is +`medians-m1.json`). Gate rule: a phase is red iff its median regresses >5% +relative AND >10ms absolute. + +Result (median-of-5 per phase, all 7 phases in band, `all_phases_passed: true` +in `medians.json`): + +| phase | baseline (ms) | current (ms) | delta | verdict | +| --- | ---: | ---: | ---: | --- | +| checkout | 56.7 | 56.9 | +0.4% | OK | +| clone | 2800.9 | 2882.8 | +2.9% | OK | +| diff | 65.6 | 25.9 | -60.6% | OK | +| edit | 5.0 | 4.9 | -1.6% | OK | +| fsck | 215.0 | 177.9 | -17.2% | OK | +| read_search | 8.8 | 8.0 | -8.6% | OK | +| status | 178.0 | 145.8 | -18.1% | OK | + +Provenance: the official quiet-window candidate/sentinel pair plus the +mandated brittle-phase repeat was recorded at `dae8aa6` (feature +m7-opt-sandbox-bringup; the same-window sentinel of the baseline binary was +green, and the first candidate run's clone red evaporated on the repeat per +the perf-contract brittle-phase rule). The pair is cited at the attested +commit per the orchestrator round-3 ruling: the only commit after `dae8aa6` +(`230c30a`, darwin seatbelt read scoping) changes `cfg(target_os = "macos")` +code, docs, and clap help text only — Linux binary perf semantics are +unchanged. Raw run JSONs (`bench-cand.json`, `bench-cand-repeat.json`, +`bench-sent.json`, with per-iteration walls) live in the mission evidence dir +`evidence/m7-cli-docs/m7-opt-sandbox-bringup/`. + +The earlier attestation-round checkout reds (rounds 1-2, bisected to +`ea416c8`) were closed with zero code change: the tax was the user's PATH +git-ai shim taking a slow path inside the run-sandbox namespace, removed by +`dae8aa6`'s universal /usr/bin/git pinning. Full history: +`bench-milestones.json` M7 rows and library/perf-contract.md "M7 checkout red +— CLOSED". + +## Per-milestone BENCH record (VAL-GATE-015) + +Every BENCH-flagged milestone held the band; the stored-artifact reds that +occurred along the way (M2 fsck, M3 clone/fsck, M4a checkout/clone, M7 +checkout) were each adjudicated per the procedure in +library/perf-contract.md — the M2-M4a set as ambient drift via same-window +sentinel reruns of the baseline code (leading to the orchestrator-signed +post-M3 rebaseline), and the M7 checkout red as the unpinned git-ai shim, +closed by harness/sandbox git pinning with an all-green pair at `dae8aa6`. +Full per-milestone table with sources: `bench-milestones.json`. + +## Cross-surface flows + +All flows executed end-to-end at the attested commit against the release +binary (30/30 checks OK; transcript `cross-surface5.log` in the mission +evidence dir, summary in `full-gate.json.cross_surface_flows`): + +- clone -> mount -> git workflow -> unmount -> remount persistence + (VAL-CROSS-001/002): clean clone status, `cross-persist` commit and hash + stable across remount. +- moved single DB file reopens (VAL-CROSS-003): exactly one file in the DB + family, `mv` + `integrity --json` + remount all green. +- sandboxed run leaves the host untouched (VAL-CROSS-004): before/after host + census identical outside `.agentfs/run/cross-host`. +- run writes visible via `agentfs fs` (VAL-CROSS-005) and the session recorded + in `agentfs timeline` (VAL-CROSS-006). +- mounted-FS and fs-CLI writes mutually visible byte-exact (VAL-CROSS-013). +- combined kill-switch leg noopen=0 noflush=0 uring=0 (VAL-CROSS-011): + create/append/fsync/unlink-while-open/git all green, remount verified, + integrity clean. + +## File-size cap census (VAL-CONS-006) + +Binding rule (architecture.md section 7): no production file over 2,500 lines +of NON-TEST CODE; sibling `tests.rs` files are excluded. The gate-enforced +census (`consistency-canon.sh` line-count-cap, cfg(test)-aware, comment/blank +stripping) reports zero offenders at the attested commit. For the audit trail, +the three files whose RAW line counts exceed 2,500 are all under the cap by +the binding rule: + +| file | raw lines | non-test code lines | +| --- | ---: | ---: | +| crates/agentfs-fuse/src/adapter/mod.rs | 3073 | 2213 | +| crates/agentfs-nfs/src/server/nfs_handlers.rs | 3827 | 2354 | +| crates/agentfs-cli/src/cmd/migrate.rs | 2546 | 1144 | + +## Notes + +- `.agents/benchmarks/restructure-baseline/attestation` is a symlink to this + directory for the mission feature's expected path. +- Benchmark runs were serialized on this machine per the perf contract; load + at each accepted run start is recorded in the run logs. +- All gate/flow harnesses pin `/usr/bin/git` with a hookless temp-HOME git + config (AGENTS.md universal-pinning rule); the post-run daemon census was + clean. diff --git a/.agents/benchmarks/restructure-attestation/bench-milestones.json b/.agents/benchmarks/restructure-attestation/bench-milestones.json new file mode 100644 index 00000000..1ec3801c --- /dev/null +++ b/.agents/benchmarks/restructure-attestation/bench-milestones.json @@ -0,0 +1,150 @@ +{ + "record": "Per-milestone BENCH compliance (VAL-GATE-015)", + "contract": "library/perf-contract.md: median-of-5 per phase via git-workload-benchmark-multi.py; red iff >5% relative AND >10ms absolute vs the active baseline; single runs never gate; serialized on a quiet machine.", + "baselines": { + "m1": { + "artifact": ".agents/benchmarks/restructure-baseline/medians-m1.json", + "recorded_at_commit": "ae1e13f (benchmarked), 809d684 (artifact commit)", + "status": "archived; superseded by the post-M3 rebaseline after three independent drift adjudications proved it brittle on this machine for diff/fsck/clone/checkout" + }, + "active_post_m3": { + "artifact": ".agents/benchmarks/restructure-baseline/medians.json", + "recorded_at_commit": "2b811f03493e861e5a7786592d25ac73b39e1aba", + "recorded_at": "2026-07-04T01:14:37Z", + "authorized_by": "orchestrator sign-off for misc-a-bench-rebaseline", + "provenance": ".agents/benchmarks/restructure-baseline/provenance-post-m3.md" + } + }, + "milestones": [ + { + "milestone": "M2 (dead-surface)", + "runs": [ + { + "source": "m2-perf-triage-bench-red (HEAD 4e37c41, 2026-07-03)", + "verdict": "in band after adjudication", + "detail": "Stored-M1 compare red on fsck (+20.6ms/+11.0%; repeat +23.7ms/+12.7%). Rebuilt baseline code ae1e13f was red against its OWN stored artifact under identical conditions (diff +37.5%, fsck +11.5%), proving ambient drift, not a HEAD regression. Adjudication accepted by M2 scrutiny (PASS) and recorded in library/perf-contract.md." + } + ] + }, + { + "milestone": "M3 (config/telemetry)", + "runs": [ + { + "source": "m3-telemetry-registry feature BENCH", + "verdict": "in band (0 red phases)", + "detail": "checkout -18.0%, clone -9.7%, diff -53.7%, edit -3.9%, fsck -0.9%, read_search +1.9%, status -7.6%." + }, + { + "source": "M3 user-testing VAL-CONF-013 (HEAD bc1753f)", + "verdict": "in band after adjudication", + "detail": "Moving red set across two stored-artifact runs (run1 clone +5.1%/fsck +8.3%; run2 diff/fsck/status); rebuilt ae1e13f sentinel red against its own artifact (clone +7.9%/+241ms, fsck +8.3%); direct HEAD-vs-sentinel showed HEAD equal or faster on the stored-red phases. Adjudicated noise; led to the orchestrator-signed post-M3 rebaseline (misc-a)." + }, + { + "source": "misc-a-bench-rebaseline (post-M3 2b811f0)", + "verdict": "baseline re-recorded", + "detail": "Accepted run pair agreed 0-red in both directions; current-HEAD confirmation vs the new baseline: 0 red phases." + } + ] + }, + { + "milestone": "M4a (core hot path)", + "runs": [ + { + "source": "m4a-store-extraction feature BENCH", + "verdict": "in band (0 red phases)", + "detail": "checkout -14.2%, clone +2.2%, diff -41.5%, edit +1.0%, fsck +4.7%, read_search -9.5%, status -15.9%." + }, + { + "source": "m4a-batcher-split feature BENCH (HEAD a687a84)", + "verdict": "in band after adjudication", + "detail": "Stored compare red on checkout/clone; rebuilt 2b811f0 sentinel equally red vs the same stored artifact under the same load; direct candidate-vs-sentinel: checkout +3.9ms, clone +3.9%, edit -0.8ms. Adjudicated ambient drift per perf-contract." + }, + { + "source": "M4a user-testing VAL-CORE-008 (HEAD 9454886, load ~3.9)", + "verdict": "in band (0 red phases)", + "detail": "bench-compare exit 0; edit 5.2ms vs 5.0ms baseline (+3.8%, +0.2ms)." + } + ] + }, + { + "milestone": "M4b (schema/overlay)", + "runs": [ + { + "source": "m4b-overlay-directoryize feature BENCH", + "verdict": "in band (0 red phases)", + "detail": "bench-compare exit 0, 7 within band." + }, + { + "source": "m4b-overlay-partial-store feature BENCH", + "verdict": "in band (0 red phases)", + "detail": "checkout +9.4ms stayed below the >10ms absolute red threshold; all other phases faster than baseline." + }, + { + "source": "m4b-agentfs-directoryize feature BENCH", + "verdict": "in band (0 red phases)", + "detail": "clone -641.5ms / -22.9%; all other phases OK." + }, + { + "source": "M4b user-testing VAL-CORE-018 (HEAD a62b0a1, load ~1.9-2.5)", + "verdict": "in band (0 red phases)", + "detail": "clone 2800.9ms -> 2741.3ms (-2.1%); diff +6.8ms and read_search +1.6ms sub-10ms absolute." + } + ] + }, + { + "milestone": "M5 (crate split)", + "runs": [ + { + "source": "m5-fuse-crate feature BENCH", + "verdict": "in band (0 red phases)", + "detail": "checkout 49.0ms, clone 2037.0ms, diff 31.1ms, edit 4.4ms, fsck 157.6ms, read_search 7.2ms, status 142.2ms." + }, + { + "source": "m5-mount-crate feature BENCH", + "verdict": "in band (0 red phases)", + "detail": "checkout 43.1ms, clone 1872.2ms, diff 42.4ms, edit 3.7ms, fsck 180.3ms, read_search 7.7ms, status 121.1ms." + }, + { + "source": "m5-fix-base-drift-coherence feature BENCH", + "verdict": "in band (0 red phases)", + "detail": "All candidate medians faster than the active baseline (checkout 50.1ms, clone 1879.4ms, diff 33.2ms, edit 3.0ms, fsck 169.8ms, read_search 6.5ms, status 118.5ms)." + } + ] + }, + { + "milestone": "M6 (semantics)", + "runs": [ + { + "source": "m6-fix-readdir-scaling feature BENCH", + "verdict": "in band (0 red phases)", + "detail": "checkout 54.6ms, clone 2605.7ms, diff 33.1ms, edit 4.5ms, fsck 206.4ms, read_search 8.1ms, status 172.3ms; bench-compare 0 red, 7 within band." + } + ] + }, + { + "milestone": "M7 (cli/docs + attestation)", + "runs": [ + { + "source": "m7-attestation round 1 (HEAD ff91302, quiet load 1.4-2.1)", + "verdict": "red (checkout) — superseded by the round-3 closure", + "detail": "Stored compare red on checkout only (56.7 -> 133.5ms, +135%); same-window sentinel did NOT clear it (sentinel 70.3ms vs HEAD 133.5ms), so it could not be adjudicated as noise; all other phases in band or faster. Deferred to a wall-time bisect." + }, + { + "source": "m7-attestation round 2 wall-time bisect (HEAD 5535368, quiet load 2.0-3.5)", + "verdict": "red reproduced; bisected to ea416c8 — superseded by the round-3 closure", + "detail": "Checkout red reproduced (104.5ms vs same-window sentinel 77.2ms). Median-of-5 wall bisect c1937a3..3669c7b in a detached worktree, alternating probe order: first bad commit ea416c8 (scope run reads; fork before tokio). Checkout-phase FUSE profile counters identical parent-vs-culprit, locating the tax in the run-sandbox environment, not the FUSE hot path. Round 2 also established the brittle-phase repeat rule after a first-run diff red (+45%) evaporated on repeat." + }, + { + "source": "m7-opt-sandbox-bringup closure (HEAD dae8aa6, quiet load 3.4-4.2)", + "verdict": "in band (0 red phases) — checkout red CLOSED with zero code change", + "detail": "Root cause: the bisect-era workload ran PATH git = the ~/.git-ai/bin/git shim inside the sandbox namespace; ea416c8's namespace pushed the shim's daemon/state logic onto a slow path per git call. dae8aa6 pins /usr/bin/git through the sandbox and harnesses, removing the tax from both legs. Official candidate/sentinel pair plus mandated brittle-phase repeat vs the stored post-M3 baseline: repeat 0 red, all 7 phases in band (checkout 56.7 -> 56.9ms +0.4%, clone +2.9%; the first candidate run's clone +24.3% red evaporated on the repeat). Same-window sentinel green; candidate checkout beat the sentinel by 17%. Exact-bench-shape zone attribution showed no measurable sandbox cost on any variant. Evidence: evidence/m7-cli-docs/m7-opt-sandbox-bringup/, library/perf-contract.md CLOSED entry." + }, + { + "source": "m7-attestation final (attested HEAD 230c30a)", + "verdict": "in band (0 red phases, cited pair)", + "detail": "medians.json records the dae8aa6 official repeat pair vs the active post-M3 baseline: all 7 phases passed (checkout +0.4%, clone +2.9%, every other phase faster than baseline). Cited at 230c30a per the orchestrator round-3 ruling: the only commit after dae8aa6 changes cfg(target_os=macos) code, docs, and clap help text only — Linux binary perf semantics unchanged." + } + ] + } + ] +} diff --git a/.agents/benchmarks/restructure-attestation/full-gate.json b/.agents/benchmarks/restructure-attestation/full-gate.json new file mode 100644 index 00000000..233c0a5f --- /dev/null +++ b/.agents/benchmarks/restructure-attestation/full-gate.json @@ -0,0 +1,131 @@ +{ + "attestation": "VFS right-thing restructure final gate (VAL-GATE-012, VAL-CROSS-012)", + "commit": "230c30a6f2e7184b0578eb2dec087b79c0f78078", + "generated_at": "2026-07-06T03:06:36+00:00", + "runner": { + "kernel": "Linux 7.1.2-3-cachyos x86_64 GNU/Linux", + "toolchain": "cargo 1.97.0-nightly (4f9b52075 2026-05-01)" + }, + "gate": { + "entrypoint": "scripts/gate.sh", + "started_at": "2026-07-06T02:57:30Z", + "ended_at": "2026-07-06T03:01:09Z", + "exit_code": 0, + "stages": [ + { + "stage": "cargo fmt --all -- --check", + "ran": true + }, + { + "stage": "cargo clippy --workspace --all-targets -- -D warnings", + "ran": true + }, + { + "stage": "cargo test --workspace", + "ran": true + }, + { + "stage": "cargo build --release --workspace --bins", + "ran": true + }, + { + "stage": "crates/agentfs-cli/tests/all.sh (strict)", + "ran": true + }, + { + "stage": "phase8-validation.py --smoke", + "ran": true + }, + { + "stage": "consistency-canon.sh", + "ran": true + } + ], + "cargo_test_suite_passed_counts": [ + 96, + 1, + 1, + 236, + 1, + 1, + 1, + 75, + 3, + 31, + 0, + 1, + 0, + 0, + 0 + ], + "shell_suite": { + "strict": true, + "summary": "PASS=24 SKIP=0 FAIL=0", + "results": { + "init": "PASS", + "cli-smoke": "PASS", + "linux-syscalls": "PASS", + "run-syscalls": "PASS", + "run-bash": "PASS", + "run-git": "PASS", + "run-read-scoping": "PASS", + "profile-error-summary": "PASS", + "ephemeral-sidecar-cleanup": "PASS", + "teardown-bounded": "PASS", + "fuse-sigint-panic-census": "PASS", + "sigkill-recovery": "PASS", + "signal-teardown": "PASS", + "corruption-torture-legacy": "PASS", + "corruption-torture-uring": "PASS", + "mount": "PASS", + "second-opener": "PASS", + "migrate-consolidation": "PASS", + "mcp-server": "PASS", + "overlay-whiteout": "PASS", + "overlay-delta-in-base-dir": "PASS", + "overlay-base-dir-rename-exdev": "PASS", + "fuse-cache-invalidation": "PASS", + "symlinks": "PASS" + } + }, + "phase8": { + "failed_gates": [], + "gates": { + "base_read_repeated_read_threshold": "passed", + "external_base_mutation_coherence": "passed", + "flush_coherence": "passed", + "fuse_serialization_parallelism": "passed", + "git_ai_process_census": "passed", + "git_workload_phase8_thresholds": "passed", + "noopen_coherence": "passed", + "phase7_validation_smoke": "passed", + "phase8_concurrent_git_stress": "passed", + "phase8_writeback_durability": "passed", + "phase8_writeback_no_fsync_crash": "passed" + } + }, + "consistency_canon": "PASS" + }, + "pjdfstest_phase5_ci": { + "command": "scripts/validation/posix/run-pjdfstest.sh --profile phase5-ci --agentfs-bin target/release/agentfs", + "result": "PASS", + "tests": "311", + "note": "run standalone at the attested HEAD" + }, + "cross_surface_flows": { + "result": "PASS", + "checks_ok": 30, + "checks_fail": 0, + "assertions": [ + "VAL-CROSS-001", + "VAL-CROSS-002", + "VAL-CROSS-003", + "VAL-CROSS-004", + "VAL-CROSS-005", + "VAL-CROSS-006", + "VAL-CROSS-011", + "VAL-CROSS-013" + ] + }, + "failed_gates": [] +} diff --git a/.agents/benchmarks/restructure-attestation/medians.json b/.agents/benchmarks/restructure-attestation/medians.json new file mode 100644 index 00000000..458c2282 --- /dev/null +++ b/.agents/benchmarks/restructure-attestation/medians.json @@ -0,0 +1,77 @@ +{ + "contract": "library/perf-contract.md: median-of-5 per phase; red iff >5% relative AND >10ms absolute vs the active baseline", + "baseline_artifact": ".agents/benchmarks/restructure-baseline/medians.json", + "baseline_commit": "2b811f03493e861e5a7786592d25ac73b39e1aba (post-M3 rebaseline, orchestrator-signed; archived M1 artifact: medians-m1.json)", + "candidate_label": "m7-opt-sandbox-bringup-repeat", + "candidate_commit": "dae8aa61ac648125d6f0ff3172c16764581eea84", + "attested_commit": "230c30a6f2e7184b0578eb2dec087b79c0f78078", + "citation_note": "Official quiet-window candidate/sentinel pair plus mandated brittle-phase repeat recorded at dae8aa6 (m7-opt-sandbox-bringup; same-window sentinel green, first-run clone red evaporated on the repeat per the round-2 brittle-phase rule). Cited at the attested HEAD per the orchestrator round-3 ruling: the only later commit (230c30a, darwin seatbelt read scoping) changes cfg(target_os=macos) code, docs, and clap help text only - Linux binary perf semantics are unchanged.", + "iterations": 5, + "invocation": "python3 scripts/validation/git-workload-benchmark-multi.py --label m7-opt-sandbox-bringup-repeat --iterations 5 --warmup 1 --agentfs-bin $PWD/target/release/agentfs --output --keep-iterations --source .agents/benchmarks/fixtures/codex --read-files 64 --read-bytes 4096 --edit-files 8", + "phases": { + "checkout": { + "baseline_median_ms": 56.684, + "baseline_median": 56.684, + "current_median_ms": 56.912, + "current_median": 56.912, + "delta_ms": 0.228, + "regression_percent": 0.4, + "passed": true + }, + "clone": { + "baseline_median_ms": 2800.892, + "baseline_median": 2800.892, + "current_median_ms": 2882.772, + "current_median": 2882.772, + "delta_ms": 81.88, + "regression_percent": 2.92, + "passed": true + }, + "diff": { + "baseline_median_ms": 65.644, + "baseline_median": 65.644, + "current_median_ms": 25.852, + "current_median": 25.852, + "delta_ms": -39.791, + "regression_percent": -60.62, + "passed": true + }, + "edit": { + "baseline_median_ms": 4.994, + "baseline_median": 4.994, + "current_median_ms": 4.912, + "current_median": 4.912, + "delta_ms": -0.081, + "regression_percent": -1.63, + "passed": true + }, + "fsck": { + "baseline_median_ms": 214.968, + "baseline_median": 214.968, + "current_median_ms": 177.918, + "current_median": 177.918, + "delta_ms": -37.05, + "regression_percent": -17.24, + "passed": true + }, + "read_search": { + "baseline_median_ms": 8.786, + "baseline_median": 8.786, + "current_median_ms": 8.03, + "current_median": 8.03, + "delta_ms": -0.756, + "regression_percent": -8.6, + "passed": true + }, + "status": { + "baseline_median_ms": 177.976, + "baseline_median": 177.976, + "current_median_ms": 145.761, + "current_median": 145.761, + "delta_ms": -32.214, + "regression_percent": -18.1, + "passed": true + } + }, + "all_phases_passed": true +} diff --git a/.agents/benchmarks/restructure-baseline/README.md b/.agents/benchmarks/restructure-baseline/README.md new file mode 100644 index 00000000..5087b7b3 --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/README.md @@ -0,0 +1,68 @@ +# Restructure Benchmark Baseline + +Authoritative performance baseline for the VFS restructure mission. Future +BENCH features compare their median-of-5 codex workload results against the +active `medians.json` artifact with the mission performance contract's +per-phase regression band. + +## Active baseline: post-M3 + +`medians.json` now contains the orchestrator-approved post-M3 rebaseline. Full +provenance, load checks, agreement checks, and recorded medians are in +`provenance-post-m3.md`. + +| Field | Value | +| --- | --- | +| Repository | `/home/ain3sh/factory/vfs` | +| Branch | `dev` | +| Commit | `2b811f03493e861e5a7786592d25ac73b39e1aba` | +| Produced at | `2026-07-04T01:14:37Z` | +| Fixture | `.agents/benchmarks/fixtures/codex` | +| AgentFS binary | `/home/ain3sh/factory/vfs/target/release/agentfs` | +| Build command | `cargo +nightly build --release --workspace --bins` | + +## Benchmark invocation + +The post-M3 active baseline was produced by the standard median-of-5 contract: + +```bash +cd /home/ain3sh/factory/vfs +python3 scripts/validation/git-workload-benchmark-multi.py \ + --label misc-a-rebaseline-run3 \ + --iterations 5 \ + --warmup 1 \ + --agentfs-bin "$PWD/target/release/agentfs" \ + --output /tmp/vfs-val/misc-a-bench-rebaseline/run3.json \ + --keep-iterations \ + --source .agents/benchmarks/fixtures/codex \ + --read-files 64 \ + --read-bytes 4096 \ + --edit-files 8 +``` + +After the confirmation checks documented in `provenance-post-m3.md`, that run +was copied into `.agents/benchmarks/restructure-baseline/medians.json`. + +## Artifact layout + +- `medians.json`: active post-M3 machine-readable aggregate used by + `scripts/validation/bench-compare.py`. +- `provenance-post-m3.md`: authoritative provenance for the active baseline. +- `medians-post-m3.json.iterations/`: raw warmup and measured iteration JSON + files for the active `medians.json`. The directory name is historical: it + disambiguates the post-M3 raw iterations from the archived M1 raw iterations, + and there is intentionally no sibling `medians-post-m3.json` file. +- `bin/agentfs`: prebuilt baseline-code sentinel binary from commit + `2b811f03493e861e5a7786592d25ac73b39e1aba`, used when ambient drift requires + a direct candidate-vs-baseline rerun. +- `bin/agentfs.sha256`: SHA-256 checksum for the cached sentinel binary. +- `medians-m1.json`: archived M1 aggregate retained for auditability. +- `medians.json.iterations/`: archived M1 warmup and measured iteration JSON + files retained for auditability. + +## Archived M1 baseline + +The original M1 baseline was produced at commit +`ae1e13fcb1c52d5c57be051610a04aa9a318f2a5` on +`2026-07-03T11:39:55Z`. It is no longer the active comparator. Use +`medians-m1.json` and `medians.json.iterations/` only for historical audits. diff --git a/.agents/benchmarks/restructure-baseline/attestation b/.agents/benchmarks/restructure-baseline/attestation new file mode 120000 index 00000000..078479b4 --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/attestation @@ -0,0 +1 @@ +../restructure-attestation \ No newline at end of file diff --git a/.agents/benchmarks/restructure-baseline/bin/agentfs b/.agents/benchmarks/restructure-baseline/bin/agentfs new file mode 100755 index 00000000..b8d5cc55 Binary files /dev/null and b/.agents/benchmarks/restructure-baseline/bin/agentfs differ diff --git a/.agents/benchmarks/restructure-baseline/bin/agentfs.sha256 b/.agents/benchmarks/restructure-baseline/bin/agentfs.sha256 new file mode 100644 index 00000000..a98a8a85 --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/bin/agentfs.sha256 @@ -0,0 +1 @@ +3062585011a5a58da5b92781b808c58856be81125ee073b972513c4e4a3c0e53 .agents/benchmarks/restructure-baseline/bin/agentfs diff --git a/.agents/benchmarks/restructure-baseline/medians-m1.json b/.agents/benchmarks/restructure-baseline/medians-m1.json new file mode 100644 index 00000000..e995d7bc --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians-m1.json @@ -0,0 +1,288 @@ +{ + "agentfs_bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "forwarded_argv": [ + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "iteration_returncodes": [ + 0, + 0, + 0, + 0, + 0 + ], + "iteration_wall_seconds": [ + 11.726330820005387, + 10.616446949003148, + 11.102209478005534, + 11.314982408992364, + 12.275473212997895 + ], + "iterations": 5, + "label": "m1-restructure-baseline", + "overall": { + "agentfs_seconds": { + "count": 5, + "max": 4.9555087110056775, + "mean": 4.306578425201588, + "median": 4.286349337999127, + "min": 3.9769298949977383, + "p25": 3.9917564260103973, + "p75": 4.322347755994997, + "stdev": 0.3967207608119313 + }, + "native_seconds": { + "count": 5, + "max": 1.4932545539923012, + "mean": 1.1113577312004055, + "median": 1.0432535720028682, + "min": 0.7422934740025084, + "p25": 1.0030864370055497, + "p75": 1.2749006189987995, + "stdev": 0.2851143345202284 + }, + "ratio": { + "count": 5, + "max": 5.377598706999959, + "mean": 4.082959409542681, + "median": 4.273160497309577, + "min": 2.894582001735038, + "p25": 3.11940384664719, + "p75": 4.750051995021641, + "stdev": 1.0604197362464365 + } + }, + "phases": { + "checkout": { + "agentfs_seconds": { + "count": 5, + "max": 0.0770113600010518, + "mean": 0.06611434899969026, + "median": 0.07058002799749374, + "min": 0.05152055200596806, + "p25": 0.0579280270030722, + "p75": 0.07353177799086552, + "stdev": 0.010882327060889053 + }, + "native_seconds": { + "count": 5, + "max": 0.20255771699885372, + "mean": 0.173984533600742, + "median": 0.1803960039978847, + "min": 0.13785997001104988, + "p25": 0.16831218400329817, + "p75": 0.18079679299262352, + "stdev": 0.02367013035810731 + }, + "ratio": { + "count": 5, + "max": 0.5333801972027974, + "mean": 0.38779891929361654, + "median": 0.3801946484294529, + "min": 0.28496385999540425, + "p25": 0.32111591010492374, + "p75": 0.4193399807355045, + "stdev": 0.09652492567369943 + } + }, + "clone": { + "agentfs_seconds": { + "count": 5, + "max": 3.744095923990244, + "mean": 3.1156066443974852, + "median": 3.039248861998203, + "min": 2.795723178991466, + "p25": 2.8421427930006757, + "p75": 3.1568224640068365, + "stdev": 0.3806691275711008 + }, + "native_seconds": { + "count": 5, + "max": 0.3831163779977942, + "mean": 0.35489527880272365, + "median": 0.3605561690055765, + "min": 0.32173172100738157, + "p25": 0.3467116099927807, + "p75": 0.3623605160100851, + "stdev": 0.022643983113979572 + }, + "ratio": { + "count": 5, + "max": 10.798876691980993, + "mean": 8.799672880660971, + "median": 8.387362109600307, + "min": 7.882663055909932, + "p25": 8.239852549516982, + "p75": 8.689609996296644, + "stdev": 1.1546899704569875 + } + }, + "diff": { + "agentfs_seconds": { + "count": 5, + "max": 0.09339736700349022, + "mean": 0.051449211800354534, + "median": 0.04466473500360735, + "min": 0.01879543399263639, + "p25": 0.0205535150016658, + "p75": 0.07983500800037291, + "stdev": 0.03403029371770566 + }, + "native_seconds": { + "count": 5, + "max": 0.4042654630029574, + "mean": 0.18929603400174527, + "median": 0.21794543499709107, + "min": 0.013877956007490866, + "p25": 0.014729614995303564, + "p75": 0.2956617010058835, + "stdev": 0.1729098286908999 + }, + "ratio": { + "count": 5, + "max": 6.729907988833325, + "mean": 2.0346072856450452, + "median": 0.2700214729495324, + "min": 0.04649280166804378, + "p25": 0.0943057834725473, + "p75": 3.032308381301778, + "stdev": 2.909999500890562 + } + }, + "edit": { + "agentfs_seconds": { + "count": 5, + "max": 0.008319867003592663, + "mean": 0.0058366957993712274, + "median": 0.005346574995201081, + "min": 0.004283816000679508, + "p25": 0.0046512179978890345, + "p75": 0.006582002999493852, + "stdev": 0.0016415414814817686 + }, + "native_seconds": { + "count": 5, + "max": 0.0009379919938510284, + "mean": 0.0006158253992907703, + "median": 0.0005357149930205196, + "min": 0.0005059730028733611, + "p25": 0.0005188820068724453, + "p75": 0.000580564999836497, + "stdev": 0.00018228950734013342 + }, + "ratio": { + "count": 5, + "max": 16.443302224318526, + "mean": 10.167255565225812, + "median": 8.011537035816739, + "min": 5.700021994057897, + "p25": 7.996445976854383, + "p75": 12.684970595081513, + "stdev": 4.331378621900962 + } + }, + "fsck": { + "agentfs_seconds": { + "count": 5, + "max": 0.2215479599981336, + "mean": 0.18989546499797144, + "median": 0.18678362200444099, + "min": 0.16708888999710325, + "p25": 0.1757984619907802, + "p75": 0.19825839099939913, + "stdev": 0.02121537116639734 + }, + "native_seconds": { + "count": 5, + "max": 0.23167631099931896, + "mean": 0.2016486482025357, + "median": 0.20147872300003655, + "min": 0.16169250100210775, + "p25": 0.19305972200527322, + "p75": 0.220335984005942, + "stdev": 0.027023921252130146 + }, + "ratio": { + "count": 5, + "max": 1.1551780128752303, + "mean": 0.9572792241188461, + "median": 0.9105910863477699, + "min": 0.72121698276521, + "p25": 0.8998003294552763, + "p75": 1.0996097091507444, + "stdev": 0.17365978585573466 + } + }, + "read_search": { + "agentfs_seconds": { + "count": 5, + "max": 0.012863876996561885, + "mean": 0.008778797800187022, + "median": 0.008317582993186079, + "min": 0.005946407007286325, + "p25": 0.007632192995515652, + "p75": 0.009133929008385167, + "stdev": 0.002567286967583289 + }, + "native_seconds": { + "count": 5, + "max": 0.008362036998732947, + "mean": 0.0066348152002319695, + "median": 0.006168587991851382, + "min": 0.005937191002885811, + "p25": 0.006128829001681879, + "p75": 0.006577431006007828, + "stdev": 0.0009933098019555761 + }, + "ratio": { + "count": 5, + "max": 2.1666604612028335, + "mean": 1.3596495637313402, + "median": 1.2645640806553191, + "min": 0.912719352553943, + "p25": 0.9639818731841785, + "p75": 1.490322051060427, + "stdev": 0.5083509298900497 + } + }, + "status": { + "agentfs_seconds": { + "count": 5, + "max": 0.18019827299576718, + "mean": 0.16797260280000045, + "median": 0.16980370599776506, + "min": 0.14666951198887546, + "p25": 0.1678003530105343, + "p75": 0.17539117000706028, + "stdev": 0.012861827612738358 + }, + "native_seconds": { + "count": 5, + "max": 0.30065786000341177, + "mean": 0.18404624820395837, + "median": 0.2312075390073005, + "min": 0.017589443988981657, + "p25": 0.13260494700807612, + "p75": 0.23817145101202186, + "stdev": 0.11079944502783666 + }, + "ratio": { + "count": 5, + "max": 8.33849620719972, + "mean": 2.339891853801501, + "median": 0.7344211470215226, + "min": 0.5993466227482706, + "p25": 0.7045359647326684, + "p75": 1.3226593273053253, + "stdev": 3.36524289528827 + } + } + }, + "warmup_iterations": 1 +} diff --git a/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-00.json b/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-00.json new file mode 100644 index 00000000..c80f921a --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-00.json @@ -0,0 +1,8674 @@ +{ + "agentfs": { + "bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "db_path": "/tmp/agentfs-git-workload-8kjmq69z/home/.agentfs/run/git-workload-d85922b0994b49d3b798ece3b36a4cc4/delta.db", + "per_phase_counters": { + "checkpoint_count": 7, + "label_count": 7, + "labels_aligned": true, + "phases": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 999403720, + "agentfs_batcher_commit_txns": 4793, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29054, + "base_fast_inode_invalidations": 15030, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create": 2, + "connection_reuse": 31221, + "connection_wait_count": 31223, + "connection_wait_nanos": 12437053, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 95, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5391, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21895, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 35, + "fuse_dispatch_wait_count": 35, + "fuse_dispatch_wait_nanos": 472375, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52472916, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5029, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11808, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 601168702, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 417336, + "fuse_op_getattr_count": 5029, + "fuse_op_getattr_nanos": 65493974, + "fuse_op_lookup_count": 11808, + "fuse_op_lookup_nanos": 162674130, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 113605096, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 31656181, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1704982, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1411465708, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 28157901, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 3321671, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32119, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1767838, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6885, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1486600 + }, + "phase": "clone", + "seq": 1 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 2, + "agentfs_batcher_commit_latency_ns_total": 4339183, + "agentfs_batcher_commit_txns": 11, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 5, + "agentfs_batcher_drains_timer": 3, + "agentfs_batcher_enqueues": 10, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 15, + "attr_cache_hits": 179, + "attr_cache_misses": 1712, + "base_fast_inode_invalidations": 50, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 14, + "connection_create": 3, + "connection_reuse": 629, + "connection_wait_count": 632, + "connection_wait_nanos": 252936, + "dentry_cache_hits": 73, + "dentry_cache_misses": 56, + "fuse_adapter_attr_hits": 4619, + "fuse_adapter_attr_misses": 634, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 35, + "fuse_adapter_inval_entry_notifications": 10, + "fuse_adapter_inval_inode_notifications": 28, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 13, + "fuse_adapter_negative_misses": 35, + "fuse_callback_count": 5324, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 5, + "fuse_dispatch_parallel_tasks": 9, + "fuse_dispatch_wait_count": 9, + "fuse_dispatch_wait_nanos": 68602, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 761360, + "fuse_flush_count": 10, + "fuse_flush_ranges": 10, + "fuse_getattr_count": 5253, + "fuse_ino_file_resolutions": 3, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 50, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 9, + "fuse_op_create_nanos": 1366681, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 9, + "fuse_op_forget_nanos": 104770, + "fuse_op_getattr_count": 5253, + "fuse_op_getattr_nanos": 18090216, + "fuse_op_lookup_count": 50, + "fuse_op_lookup_nanos": 807787, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 15, + "fuse_op_other_nanos": 3407466, + "fuse_op_read_count": 8, + "fuse_op_read_nanos": 1458377, + "fuse_op_readdirplus_count": 4, + "fuse_op_readdirplus_nanos": 1396155, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 14, + "fuse_op_setattr_nanos": 5080395, + "fuse_op_write_count": 9, + "fuse_op_write_nanos": 447252, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 8, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 698, + "fuse_read_lane_wait_nanos": 110521, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 4, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5362, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 591393, + "fuse_write_count": 9, + "fuse_write_lane_wait_count": 42, + "fuse_write_lane_wait_nanos": 73587, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 1294, + "lookup_base_count": 0, + "lookup_count": 164, + "lookup_delta_count": 60, + "lookup_whiteout_count": 0, + "negative_cache_hits": 34, + "negative_cache_invalidations": 26, + "negative_cache_misses": 148, + "negative_lookup_count": 86, + "path_cache_hits": 73, + "path_cache_misses": 56, + "path_component_count": 124, + "path_resolution_count": 45, + "readdir_count": 0, + "readdir_plus_count": 4, + "wal_checkpoint_count": 2, + "wal_checkpoint_nanos": 893061 + }, + "phase": "checkout", + "seq": 2 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 4, + "agentfs_batcher_commit_latency_ns_total": 4982572, + "agentfs_batcher_commit_txns": 4, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 2, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 6, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 4, + "attr_cache_hits": 18, + "attr_cache_misses": 32, + "base_fast_inode_invalidations": 24, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 20, + "chunk_read_queries": 12, + "chunk_write_chunks": 20, + "connection_create": 0, + "connection_reuse": 1704, + "connection_wait_count": 1704, + "connection_wait_nanos": 401284, + "dentry_cache_hits": 19, + "dentry_cache_misses": 570, + "fuse_adapter_attr_hits": 1221, + "fuse_adapter_attr_misses": 5, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 289, + "fuse_adapter_inval_entry_notifications": 4, + "fuse_adapter_inval_inode_notifications": 8, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 289, + "fuse_callback_count": 2920, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 4, + "fuse_dispatch_wait_count": 4, + "fuse_dispatch_wait_nanos": 99861, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 1181458, + "fuse_flush_count": 6, + "fuse_flush_ranges": 6, + "fuse_getattr_count": 1226, + "fuse_ino_file_resolutions": 2, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 291, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 2, + "fuse_op_create_nanos": 947724, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 4, + "fuse_op_forget_nanos": 100028, + "fuse_op_getattr_count": 1226, + "fuse_op_getattr_nanos": 2066553, + "fuse_op_lookup_count": 291, + "fuse_op_lookup_nanos": 5011419, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 2762, + "fuse_op_other_nanos": 3392403, + "fuse_op_read_count": 12, + "fuse_op_read_nanos": 2375689, + "fuse_op_readdirplus_count": 1381, + "fuse_op_readdirplus_nanos": 62337425, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 6, + "fuse_op_setattr_nanos": 8393400, + "fuse_op_write_count": 10, + "fuse_op_write_nanos": 2325205, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 12, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 994, + "fuse_read_lane_wait_nanos": 165850, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1381, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5690, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 1181458, + "fuse_write_count": 10, + "fuse_write_lane_wait_count": 14, + "fuse_write_lane_wait_nanos": 54764, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 22, + "lookup_base_count": 0, + "lookup_count": 598, + "lookup_delta_count": 295, + "lookup_whiteout_count": 0, + "negative_cache_hits": 4, + "negative_cache_invalidations": 4, + "negative_cache_misses": 873, + "negative_lookup_count": 578, + "path_cache_hits": 19, + "path_cache_misses": 570, + "path_component_count": 4606, + "path_resolution_count": 977, + "readdir_count": 0, + "readdir_plus_count": 1376, + "wal_checkpoint_count": 688, + "wal_checkpoint_nanos": 11765788 + }, + "phase": "status", + "seq": 3 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 1, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 8, + "connection_wait_count": 8, + "connection_wait_nanos": 8021, + "dentry_cache_hits": 1, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 69, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 1, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 1, + "fuse_callback_count": 76, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 69, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 1, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 69, + "fuse_op_getattr_nanos": 71779, + "fuse_op_lookup_count": 1, + "fuse_op_lookup_nanos": 27439, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 6, + "fuse_op_read_nanos": 1420250, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 6, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 2, + "fuse_read_lane_wait_nanos": 1008, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 76, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 2, + "lookup_delta_count": 1, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 2, + "negative_lookup_count": 0, + "path_cache_hits": 1, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "read_search", + "seq": 4 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 1616869, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 8, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 8, + "attr_cache_hits": 0, + "attr_cache_misses": 24, + "base_fast_inode_invalidations": 16, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 32, + "connection_wait_count": 32, + "connection_wait_nanos": 16960, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 16, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 24, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 6398, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 16, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 16, + "fuse_op_getattr_nanos": 29738, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 8, + "fuse_op_other_nanos": 1091557, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 8, + "fuse_op_setattr_nanos": 2922583, + "fuse_op_write_count": 8, + "fuse_op_write_nanos": 525446, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 8, + "fuse_read_lane_wait_nanos": 2957, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 40, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 6398, + "fuse_write_count": 8, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 2128, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 16, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 8, + "wal_checkpoint_nanos": 925447 + }, + "phase": "edit", + "seq": 5 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 1426, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 89, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 1515, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 1426, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 89, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 1426, + "fuse_op_getattr_nanos": 941692, + "fuse_op_lookup_count": 89, + "fuse_op_lookup_nanos": 111530, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 12, + "fuse_op_other_nanos": 6943, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 1527, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 89, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "diff", + "seq": 6 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 3, + "attr_cache_misses": 2, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 1, + "chunk_read_queries": 1, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 24, + "connection_wait_count": 24, + "connection_wait_nanos": 17174, + "dentry_cache_hits": 4, + "dentry_cache_misses": 2, + "fuse_adapter_attr_hits": 44, + "fuse_adapter_attr_misses": 1, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 4, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 236, + "fuse_adapter_negative_misses": 4, + "fuse_callback_count": 305, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 45, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 240, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 45, + "fuse_op_getattr_nanos": 72787, + "fuse_op_lookup_count": 240, + "fuse_op_lookup_nanos": 320427, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 36, + "fuse_op_other_nanos": 15240, + "fuse_op_read_count": 2, + "fuse_op_read_nanos": 361359, + "fuse_op_readdirplus_count": 18, + "fuse_op_readdirplus_nanos": 1149600, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 2, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 15, + "fuse_read_lane_wait_nanos": 4579, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 18, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 341, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 2, + "lookup_base_count": 0, + "lookup_count": 8, + "lookup_delta_count": 4, + "lookup_whiteout_count": 0, + "negative_cache_hits": 236, + "negative_cache_invalidations": 0, + "negative_cache_misses": 9, + "negative_lookup_count": 2, + "path_cache_hits": 4, + "path_cache_misses": 2, + "path_component_count": 42, + "path_resolution_count": 10, + "readdir_count": 0, + "readdir_plus_count": 18, + "wal_checkpoint_count": 9, + "wal_checkpoint_nanos": 350709 + }, + "phase": "fsck", + "seq": 7 + } + ] + }, + "profile_counters": { + "last_by_source": { + "cli": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26122, + "attr_cache_misses": 30824, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33621, + "connection_wait_count": 33626, + "connection_wait_nanos": 13139933, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7490, + "fuse_adapter_attr_misses": 5574, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5729, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32059, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13064, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12479, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13064, + "fuse_op_getattr_nanos": 86766739, + "fuse_op_lookup_count": 12479, + "fuse_op_lookup_nanos": 168952732, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 121518705, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 37271856, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 66588162, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 3606586, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45155, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1899928, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20670, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7248, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 15602480 + }, + "phase-checkpoint-1": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 999403720, + "agentfs_batcher_commit_txns": 4793, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29054, + "base_fast_inode_invalidations": 15030, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create": 2, + "connection_reuse": 31221, + "connection_wait_count": 31223, + "connection_wait_nanos": 12437053, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 95, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5391, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21895, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 35, + "fuse_dispatch_wait_count": 35, + "fuse_dispatch_wait_nanos": 472375, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52472916, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5029, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11808, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 601168702, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 417336, + "fuse_op_getattr_count": 5029, + "fuse_op_getattr_nanos": 65493974, + "fuse_op_lookup_count": 11808, + "fuse_op_lookup_nanos": 162674130, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 113605096, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 31656181, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1704982, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1411465708, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 28157901, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 3321671, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32119, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1767838, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6885, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1486600 + }, + "phase-checkpoint-2": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 1003742903, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26100, + "attr_cache_misses": 30766, + "base_fast_inode_invalidations": 15080, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 5, + "connection_reuse": 31850, + "connection_wait_count": 31855, + "connection_wait_nanos": 12689989, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4714, + "fuse_adapter_attr_misses": 5568, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27219, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 540977, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10282, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11858, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 602535383, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 522106, + "fuse_op_getattr_count": 10282, + "fuse_op_getattr_nanos": 83584190, + "fuse_op_lookup_count": 11858, + "fuse_op_lookup_nanos": 163481917, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 117012562, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 33114558, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3101137, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1416546103, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 28605153, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 16851, + "fuse_read_lane_wait_nanos": 3432192, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37481, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1841425, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20630, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6919, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2379661 + }, + "phase-checkpoint-3": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1008725475, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26118, + "attr_cache_misses": 30798, + "base_fast_inode_invalidations": 15104, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33554, + "connection_wait_count": 33559, + "connection_wait_nanos": 13091273, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5935, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30139, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11508, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12149, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11508, + "fuse_op_getattr_nanos": 85650743, + "fuse_op_lookup_count": 12149, + "fuse_op_lookup_nanos": 168493336, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 120404965, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 35490247, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1424939503, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30930358, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17845, + "fuse_read_lane_wait_nanos": 3598042, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43171, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1896189, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20652, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14145449 + }, + "phase-checkpoint-4": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1008725475, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26119, + "attr_cache_misses": 30798, + "base_fast_inode_invalidations": 15104, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33562, + "connection_wait_count": 33567, + "connection_wait_nanos": 13099294, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6004, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30215, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11577, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12150, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11577, + "fuse_op_getattr_nanos": 85722522, + "fuse_op_lookup_count": 12150, + "fuse_op_lookup_nanos": 168520775, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 120404965, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1424939503, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30930358, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17847, + "fuse_read_lane_wait_nanos": 3599050, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43247, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1896189, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20652, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14145449 + }, + "phase-checkpoint-5": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26119, + "attr_cache_misses": 30822, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33594, + "connection_wait_count": 33599, + "connection_wait_nanos": 13116254, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6020, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30239, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11593, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12150, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11593, + "fuse_op_getattr_nanos": 85752260, + "fuse_op_lookup_count": 12150, + "fuse_op_lookup_nanos": 168520775, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 121496522, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17855, + "fuse_read_lane_wait_nanos": 3602007, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43287, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20668, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 15070896 + }, + "phase-checkpoint-6": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26119, + "attr_cache_misses": 30822, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33594, + "connection_wait_count": 33599, + "connection_wait_nanos": 13116254, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7446, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5493, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31754, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13019, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12239, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13019, + "fuse_op_getattr_nanos": 86693952, + "fuse_op_lookup_count": 12239, + "fuse_op_lookup_nanos": 168632305, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 121503465, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17855, + "fuse_read_lane_wait_nanos": 3602007, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44814, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20668, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7012, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 15070896 + }, + "phase-checkpoint-7": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26122, + "attr_cache_misses": 30824, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33618, + "connection_wait_count": 33623, + "connection_wait_nanos": 13133428, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7490, + "fuse_adapter_attr_misses": 5574, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5729, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32059, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13064, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12479, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13064, + "fuse_op_getattr_nanos": 86766739, + "fuse_op_lookup_count": 12479, + "fuse_op_lookup_nanos": 168952732, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 121518705, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 37271856, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 66588162, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 3606586, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45155, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20670, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7248, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 15421605 + } + }, + "max_counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26122, + "attr_cache_misses": 30824, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33621, + "connection_wait_count": 33626, + "connection_wait_nanos": 13139933, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7490, + "fuse_adapter_attr_misses": 5574, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5729, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32059, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13064, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12479, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13064, + "fuse_op_getattr_nanos": 86766739, + "fuse_op_lookup_count": 12479, + "fuse_op_lookup_nanos": 168952732, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 121518705, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 37271856, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 66588162, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 3606586, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45155, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1899928, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20670, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7248, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 15602480 + }, + "summary_count": 8 + }, + "profile_enabled": true, + "profile_summary_count": 8, + "session": "git-workload-d85922b0994b49d3b798ece3b36a4cc4" + }, + "agentfs_overlay": { + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 999403720, + "agentfs_batcher_commit_txns": 4793, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29054, + "base_fast_inode_invalidations": 15030, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create": 2, + "connection_reuse": 31221, + "connection_wait_count": 31223, + "connection_wait_nanos": 12437053, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 95, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5391, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21895, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 35, + "fuse_dispatch_wait_count": 35, + "fuse_dispatch_wait_nanos": 472375, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52472916, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5029, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11808, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 601168702, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 417336, + "fuse_op_getattr_count": 5029, + "fuse_op_getattr_nanos": 65493974, + "fuse_op_lookup_count": 11808, + "fuse_op_lookup_nanos": 162674130, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 113605096, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 31656181, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1704982, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1411465708, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 28157901, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 3321671, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32119, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1767838, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6885, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1486600 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15030, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 999403720, + "agentfs_batcher_commit_txns": 4793, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29054, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create": 2, + "connection_reuse": 31221, + "connection_wait_count": 31223, + "connection_wait_nanos": 12437053, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6885, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1486600 + }, + "fuse": { + "fuse_adapter_attr_hits": 95, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5391, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21895, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 35, + "fuse_dispatch_wait_count": 35, + "fuse_dispatch_wait_nanos": 472375, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52472916, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5029, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11808, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 601168702, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 417336, + "fuse_op_getattr_count": 5029, + "fuse_op_getattr_nanos": 65493974, + "fuse_op_lookup_count": 11808, + "fuse_op_lookup_nanos": 162674130, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 113605096, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 31656181, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1704982, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1411465708, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 28157901, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 3321671, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 32119, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1767838 + } + }, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 1003742903, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26100, + "attr_cache_misses": 30766, + "base_fast_inode_invalidations": 15080, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 5, + "connection_reuse": 31850, + "connection_wait_count": 31855, + "connection_wait_nanos": 12689989, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4714, + "fuse_adapter_attr_misses": 5568, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27219, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 540977, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10282, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11858, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 602535383, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 522106, + "fuse_op_getattr_count": 10282, + "fuse_op_getattr_nanos": 83584190, + "fuse_op_lookup_count": 11858, + "fuse_op_lookup_nanos": 163481917, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 117012562, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 33114558, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3101137, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1416546103, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 28605153, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 16851, + "fuse_read_lane_wait_nanos": 3432192, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37481, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1841425, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20630, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6919, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2379661 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15080, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 1003742903, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26100, + "attr_cache_misses": 30766, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 5, + "connection_reuse": 31850, + "connection_wait_count": 31855, + "connection_wait_nanos": 12689989, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "getattr_count": 20630, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6919, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2379661 + }, + "fuse": { + "fuse_adapter_attr_hits": 4714, + "fuse_adapter_attr_misses": 5568, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27219, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 540977, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10282, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11858, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 602535383, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 522106, + "fuse_op_getattr_count": 10282, + "fuse_op_getattr_nanos": 83584190, + "fuse_op_lookup_count": 11858, + "fuse_op_lookup_nanos": 163481917, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 117012562, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 33114558, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3101137, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1416546103, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 28605153, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 16851, + "fuse_read_lane_wait_nanos": 3432192, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 37481, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1841425 + } + }, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1008725475, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26118, + "attr_cache_misses": 30798, + "base_fast_inode_invalidations": 15104, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33554, + "connection_wait_count": 33559, + "connection_wait_nanos": 13091273, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5935, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30139, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11508, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12149, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11508, + "fuse_op_getattr_nanos": 85650743, + "fuse_op_lookup_count": 12149, + "fuse_op_lookup_nanos": 168493336, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 120404965, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 35490247, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1424939503, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30930358, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17845, + "fuse_read_lane_wait_nanos": 3598042, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43171, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1896189, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20652, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14145449 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15104, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1008725475, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26118, + "attr_cache_misses": 30798, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33554, + "connection_wait_count": 33559, + "connection_wait_nanos": 13091273, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "getattr_count": 20652, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14145449 + }, + "fuse": { + "fuse_adapter_attr_hits": 5935, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30139, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11508, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12149, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11508, + "fuse_op_getattr_nanos": 85650743, + "fuse_op_lookup_count": 12149, + "fuse_op_lookup_nanos": 168493336, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 120404965, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 35490247, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1424939503, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30930358, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17845, + "fuse_read_lane_wait_nanos": 3598042, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43171, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1896189 + } + }, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1008725475, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26119, + "attr_cache_misses": 30798, + "base_fast_inode_invalidations": 15104, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33562, + "connection_wait_count": 33567, + "connection_wait_nanos": 13099294, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6004, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30215, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11577, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12150, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11577, + "fuse_op_getattr_nanos": 85722522, + "fuse_op_lookup_count": 12150, + "fuse_op_lookup_nanos": 168520775, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 120404965, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1424939503, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30930358, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17847, + "fuse_read_lane_wait_nanos": 3599050, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43247, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1896189, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20652, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14145449 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15104, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1008725475, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26119, + "attr_cache_misses": 30798, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33562, + "connection_wait_count": 33567, + "connection_wait_nanos": 13099294, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20652, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14145449 + }, + "fuse": { + "fuse_adapter_attr_hits": 6004, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30215, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11577, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12150, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11577, + "fuse_op_getattr_nanos": 85722522, + "fuse_op_lookup_count": 12150, + "fuse_op_lookup_nanos": 168520775, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 120404965, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1424939503, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30930358, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17847, + "fuse_read_lane_wait_nanos": 3599050, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43247, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1896189 + } + }, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26119, + "attr_cache_misses": 30822, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33594, + "connection_wait_count": 33599, + "connection_wait_nanos": 13116254, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6020, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30239, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11593, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12150, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11593, + "fuse_op_getattr_nanos": 85752260, + "fuse_op_lookup_count": 12150, + "fuse_op_lookup_nanos": 168520775, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 121496522, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17855, + "fuse_read_lane_wait_nanos": 3602007, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43287, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20668, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 15070896 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26119, + "attr_cache_misses": 30822, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33594, + "connection_wait_count": 33599, + "connection_wait_nanos": 13116254, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20668, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 15070896 + }, + "fuse": { + "fuse_adapter_attr_hits": 6020, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30239, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11593, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12150, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11593, + "fuse_op_getattr_nanos": 85752260, + "fuse_op_lookup_count": 12150, + "fuse_op_lookup_nanos": 168520775, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 121496522, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17855, + "fuse_read_lane_wait_nanos": 3602007, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43287, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317 + } + }, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26119, + "attr_cache_misses": 30822, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33594, + "connection_wait_count": 33599, + "connection_wait_nanos": 13116254, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7446, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5493, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31754, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13019, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12239, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13019, + "fuse_op_getattr_nanos": 86693952, + "fuse_op_lookup_count": 12239, + "fuse_op_lookup_nanos": 168632305, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 121503465, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17855, + "fuse_read_lane_wait_nanos": 3602007, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44814, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20668, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7012, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 15070896 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26119, + "attr_cache_misses": 30822, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33594, + "connection_wait_count": 33599, + "connection_wait_nanos": 13116254, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20668, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7012, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 15070896 + }, + "fuse": { + "fuse_adapter_attr_hits": 7446, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5493, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31754, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13019, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12239, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13019, + "fuse_op_getattr_nanos": 86693952, + "fuse_op_lookup_count": 12239, + "fuse_op_lookup_nanos": 168632305, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 121503465, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17855, + "fuse_read_lane_wait_nanos": 3602007, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 44814, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317 + } + }, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26122, + "attr_cache_misses": 30824, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33618, + "connection_wait_count": 33623, + "connection_wait_nanos": 13133428, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7490, + "fuse_adapter_attr_misses": 5574, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5729, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32059, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13064, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12479, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13064, + "fuse_op_getattr_nanos": 86766739, + "fuse_op_lookup_count": 12479, + "fuse_op_lookup_nanos": 168952732, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 121518705, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 37271856, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 66588162, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 3606586, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45155, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20670, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7248, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 15421605 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26122, + "attr_cache_misses": 30824, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33618, + "connection_wait_count": 33623, + "connection_wait_nanos": 13133428, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "getattr_count": 20670, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7248, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 15421605 + }, + "fuse": { + "fuse_adapter_attr_hits": 7490, + "fuse_adapter_attr_misses": 5574, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5729, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32059, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13064, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12479, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13064, + "fuse_op_getattr_nanos": 86766739, + "fuse_op_lookup_count": 12479, + "fuse_op_lookup_nanos": 168952732, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 121518705, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 37271856, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 66588162, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 3606586, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45155, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317 + } + }, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26122, + "attr_cache_misses": 30824, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33621, + "connection_wait_count": 33626, + "connection_wait_nanos": 13139933, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7490, + "fuse_adapter_attr_misses": 5574, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5729, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32059, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13064, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12479, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13064, + "fuse_op_getattr_nanos": 86766739, + "fuse_op_lookup_count": 12479, + "fuse_op_lookup_nanos": 168952732, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 121518705, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 37271856, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 66588162, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 3606586, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45155, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1899928, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20670, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7248, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 15602480 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26122, + "attr_cache_misses": 30824, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33621, + "connection_wait_count": 33626, + "connection_wait_nanos": 13139933, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "getattr_count": 20670, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7248, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 15602480 + }, + "fuse": { + "fuse_adapter_attr_hits": 7490, + "fuse_adapter_attr_misses": 5574, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5729, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32059, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13064, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12479, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13064, + "fuse_op_getattr_nanos": 86766739, + "fuse_op_lookup_count": 12479, + "fuse_op_lookup_nanos": 168952732, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 121518705, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 37271856, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 66588162, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 3606586, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45155, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1899928 + } + }, + "source": "cli" + } + ], + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-d85922b0994b49d3b798ece3b36a4cc4", + "--no-default-allows", + "--", + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/agentfs-base", + "duration_seconds": 4.889367215000675, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 999403720, + "agentfs_batcher_commit_txns": 4793, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29054, + "base_fast_inode_invalidations": 15030, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create": 2, + "connection_reuse": 31221, + "connection_wait_count": 31223, + "connection_wait_nanos": 12437053, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 95, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5391, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21895, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 35, + "fuse_dispatch_wait_count": 35, + "fuse_dispatch_wait_nanos": 472375, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52472916, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5029, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11808, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 601168702, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 417336, + "fuse_op_getattr_count": 5029, + "fuse_op_getattr_nanos": 65493974, + "fuse_op_lookup_count": 11808, + "fuse_op_lookup_nanos": 162674130, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 113605096, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 31656181, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1704982, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1411465708, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 28157901, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 3321671, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32119, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1767838, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6885, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1486600 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15030, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 999403720, + "agentfs_batcher_commit_txns": 4793, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29054, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create": 2, + "connection_reuse": 31221, + "connection_wait_count": 31223, + "connection_wait_nanos": 12437053, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6885, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1486600 + }, + "fuse": { + "fuse_adapter_attr_hits": 95, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5391, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21895, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 35, + "fuse_dispatch_wait_count": 35, + "fuse_dispatch_wait_nanos": 472375, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52472916, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5029, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11808, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 601168702, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 417336, + "fuse_op_getattr_count": 5029, + "fuse_op_getattr_nanos": 65493974, + "fuse_op_lookup_count": 11808, + "fuse_op_lookup_nanos": 162674130, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 113605096, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 31656181, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1704982, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1411465708, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 28157901, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 3321671, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 32119, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1767838 + } + }, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 1003742903, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26100, + "attr_cache_misses": 30766, + "base_fast_inode_invalidations": 15080, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 5, + "connection_reuse": 31850, + "connection_wait_count": 31855, + "connection_wait_nanos": 12689989, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4714, + "fuse_adapter_attr_misses": 5568, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27219, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 540977, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10282, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11858, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 602535383, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 522106, + "fuse_op_getattr_count": 10282, + "fuse_op_getattr_nanos": 83584190, + "fuse_op_lookup_count": 11858, + "fuse_op_lookup_nanos": 163481917, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 117012562, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 33114558, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3101137, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1416546103, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 28605153, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 16851, + "fuse_read_lane_wait_nanos": 3432192, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37481, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1841425, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20630, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6919, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2379661 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15080, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 1003742903, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26100, + "attr_cache_misses": 30766, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 5, + "connection_reuse": 31850, + "connection_wait_count": 31855, + "connection_wait_nanos": 12689989, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "getattr_count": 20630, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6919, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2379661 + }, + "fuse": { + "fuse_adapter_attr_hits": 4714, + "fuse_adapter_attr_misses": 5568, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27219, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 540977, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10282, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11858, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 602535383, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 522106, + "fuse_op_getattr_count": 10282, + "fuse_op_getattr_nanos": 83584190, + "fuse_op_lookup_count": 11858, + "fuse_op_lookup_nanos": 163481917, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 117012562, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 33114558, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3101137, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1416546103, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 28605153, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 16851, + "fuse_read_lane_wait_nanos": 3432192, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 37481, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1841425 + } + }, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1008725475, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26118, + "attr_cache_misses": 30798, + "base_fast_inode_invalidations": 15104, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33554, + "connection_wait_count": 33559, + "connection_wait_nanos": 13091273, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5935, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30139, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11508, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12149, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11508, + "fuse_op_getattr_nanos": 85650743, + "fuse_op_lookup_count": 12149, + "fuse_op_lookup_nanos": 168493336, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 120404965, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 35490247, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1424939503, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30930358, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17845, + "fuse_read_lane_wait_nanos": 3598042, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43171, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1896189, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20652, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14145449 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15104, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1008725475, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26118, + "attr_cache_misses": 30798, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33554, + "connection_wait_count": 33559, + "connection_wait_nanos": 13091273, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "getattr_count": 20652, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14145449 + }, + "fuse": { + "fuse_adapter_attr_hits": 5935, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30139, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11508, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12149, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11508, + "fuse_op_getattr_nanos": 85650743, + "fuse_op_lookup_count": 12149, + "fuse_op_lookup_nanos": 168493336, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 120404965, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 35490247, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1424939503, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30930358, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17845, + "fuse_read_lane_wait_nanos": 3598042, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43171, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1896189 + } + }, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1008725475, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26119, + "attr_cache_misses": 30798, + "base_fast_inode_invalidations": 15104, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33562, + "connection_wait_count": 33567, + "connection_wait_nanos": 13099294, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6004, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30215, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11577, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12150, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11577, + "fuse_op_getattr_nanos": 85722522, + "fuse_op_lookup_count": 12150, + "fuse_op_lookup_nanos": 168520775, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 120404965, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1424939503, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30930358, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17847, + "fuse_read_lane_wait_nanos": 3599050, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43247, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1896189, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20652, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14145449 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15104, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1008725475, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26119, + "attr_cache_misses": 30798, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33562, + "connection_wait_count": 33567, + "connection_wait_nanos": 13099294, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20652, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14145449 + }, + "fuse": { + "fuse_adapter_attr_hits": 6004, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30215, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11577, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12150, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11577, + "fuse_op_getattr_nanos": 85722522, + "fuse_op_lookup_count": 12150, + "fuse_op_lookup_nanos": 168520775, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 120404965, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1424939503, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30930358, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17847, + "fuse_read_lane_wait_nanos": 3599050, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43247, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1896189 + } + }, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26119, + "attr_cache_misses": 30822, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33594, + "connection_wait_count": 33599, + "connection_wait_nanos": 13116254, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6020, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30239, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11593, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12150, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11593, + "fuse_op_getattr_nanos": 85752260, + "fuse_op_lookup_count": 12150, + "fuse_op_lookup_nanos": 168520775, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 121496522, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17855, + "fuse_read_lane_wait_nanos": 3602007, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43287, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20668, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 15070896 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26119, + "attr_cache_misses": 30822, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33594, + "connection_wait_count": 33599, + "connection_wait_nanos": 13116254, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20668, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 15070896 + }, + "fuse": { + "fuse_adapter_attr_hits": 6020, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5404, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30239, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11593, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12150, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 11593, + "fuse_op_getattr_nanos": 85752260, + "fuse_op_lookup_count": 12150, + "fuse_op_lookup_nanos": 168520775, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 121496522, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17855, + "fuse_read_lane_wait_nanos": 3602007, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43287, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317 + } + }, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26119, + "attr_cache_misses": 30822, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33594, + "connection_wait_count": 33599, + "connection_wait_nanos": 13116254, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7446, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5493, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31754, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13019, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12239, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13019, + "fuse_op_getattr_nanos": 86693952, + "fuse_op_lookup_count": 12239, + "fuse_op_lookup_nanos": 168632305, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 121503465, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17855, + "fuse_read_lane_wait_nanos": 3602007, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44814, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20668, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7012, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 15070896 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26119, + "attr_cache_misses": 30822, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33594, + "connection_wait_count": 33599, + "connection_wait_nanos": 13116254, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20668, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7012, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 15070896 + }, + "fuse": { + "fuse_adapter_attr_hits": 7446, + "fuse_adapter_attr_misses": 5573, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5493, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31754, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13019, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12239, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13019, + "fuse_op_getattr_nanos": 86693952, + "fuse_op_lookup_count": 12239, + "fuse_op_lookup_nanos": 168632305, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 121503465, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 36910497, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65438562, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17855, + "fuse_read_lane_wait_nanos": 3602007, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 44814, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317 + } + }, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26122, + "attr_cache_misses": 30824, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33618, + "connection_wait_count": 33623, + "connection_wait_nanos": 13133428, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7490, + "fuse_adapter_attr_misses": 5574, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5729, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32059, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13064, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12479, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13064, + "fuse_op_getattr_nanos": 86766739, + "fuse_op_lookup_count": 12479, + "fuse_op_lookup_nanos": 168952732, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 121518705, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 37271856, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 66588162, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 3606586, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45155, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20670, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7248, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 15421605 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26122, + "attr_cache_misses": 30824, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33618, + "connection_wait_count": 33623, + "connection_wait_nanos": 13133428, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "getattr_count": 20670, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7248, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 15421605 + }, + "fuse": { + "fuse_adapter_attr_hits": 7490, + "fuse_adapter_attr_misses": 5574, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5729, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32059, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13064, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12479, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13064, + "fuse_op_getattr_nanos": 86766739, + "fuse_op_lookup_count": 12479, + "fuse_op_lookup_nanos": 168952732, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 121518705, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 37271856, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 66588162, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 3606586, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45155, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1898317 + } + }, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26122, + "attr_cache_misses": 30824, + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33621, + "connection_wait_count": 33626, + "connection_wait_nanos": 13139933, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7490, + "fuse_adapter_attr_misses": 5574, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5729, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32059, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13064, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12479, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13064, + "fuse_op_getattr_nanos": 86766739, + "fuse_op_lookup_count": 12479, + "fuse_op_lookup_nanos": 168952732, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 121518705, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 37271856, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 66588162, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 3606586, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45155, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1899928, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20670, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7248, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 15602480 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15120, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 1010342344, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1376256, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26122, + "attr_cache_misses": 30824, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 5, + "connection_reuse": 33621, + "connection_wait_count": 33626, + "connection_wait_nanos": 13139933, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "getattr_count": 20670, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7248, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 15602480 + }, + "fuse": { + "fuse_adapter_attr_hits": 7490, + "fuse_adapter_attr_misses": 5574, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5729, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32059, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 640838, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13064, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12479, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 603483107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 638, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 622134, + "fuse_op_getattr_count": 13064, + "fuse_op_getattr_nanos": 86766739, + "fuse_op_lookup_count": 12479, + "fuse_op_lookup_nanos": 168952732, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 744, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 121518705, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 37271856, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 66588162, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1427862086, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 31455804, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 3606586, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45155, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1899928 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 64598, + "stderr_tail": "en_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":1010342344,\"agentfs_batcher_commit_txns\":4816,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1376256,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26119,\"attr_cache_misses\":30822,\"chunk_read_chunks\":267,\"chunk_read_queries\":122,\"chunk_write_chunks\":1002,\"connection_create\":5,\"connection_reuse\":33594,\"connection_wait_count\":33599,\"connection_wait_nanos\":13116254,\"dentry_cache_hits\":26125,\"dentry_cache_misses\":14052,\"getattr_count\":20668,\"lookup_base_count\":23,\"lookup_count\":41052,\"lookup_delta_count\":7547,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7012,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46085,\"negative_lookup_count\":14920,\"path_cache_hits\":26125,\"path_cache_misses\":14052,\"path_component_count\":37632,\"path_resolution_count\":8140,\"readdir_count\":1,\"readdir_plus_count\":1398,\"wal_checkpoint_count\":708,\"wal_checkpoint_nanos\":15070896},\"fuse\":{\"fuse_adapter_attr_hits\":7446,\"fuse_adapter_attr_misses\":5573,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6734,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5493,\"fuse_adapter_negative_misses\":6734,\"fuse_callback_count\":31754,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":48,\"fuse_dispatch_wait_count\":48,\"fuse_dispatch_wait_nanos\":640838,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13019,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12239,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":603483107,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":638,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":622134,\"fuse_op_getattr_count\":13019,\"fuse_op_getattr_nanos\":86693952,\"fuse_op_lookup_count\":12239,\"fuse_op_lookup_nanos\":168632305,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":744,\"fuse_op_other_count\":3580,\"fuse_op_other_nanos\":121503465,\"fuse_op_read_count\":239,\"fuse_op_read_nanos\":36910497,\"fuse_op_readdirplus_count\":1407,\"fuse_op_readdirplus_nanos\":65438562,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1427862086,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":31455804,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":239,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17855,\"fuse_read_lane_wait_nanos\":3602007,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1407,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":44814,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1898317}},\"source\":\"phase-checkpoint-6\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":1010342344,\"agentfs_batcher_commit_txns\":4816,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1376256,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26122,\"attr_cache_misses\":30824,\"base_fast_inode_invalidations\":15120,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1002,\"connection_create\":5,\"connection_reuse\":33618,\"connection_wait_count\":33623,\"connection_wait_nanos\":13133428,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7490,\"fuse_adapter_attr_misses\":5574,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5729,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32059,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":48,\"fuse_dispatch_wait_count\":48,\"fuse_dispatch_wait_nanos\":640838,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13064,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12479,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":603483107,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":638,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":622134,\"fuse_op_getattr_count\":13064,\"fuse_op_getattr_nanos\":86766739,\"fuse_op_lookup_count\":12479,\"fuse_op_lookup_nanos\":168952732,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":744,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":121518705,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":37271856,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":66588162,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1427862086,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":31455804,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17870,\"fuse_read_lane_wait_nanos\":3606586,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45155,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1898317,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20670,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7248,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":717,\"wal_checkpoint_nanos\":15421605},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":15120,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":1010342344,\"agentfs_batcher_commit_txns\":4816,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1376256,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26122,\"attr_cache_misses\":30824,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1002,\"connection_create\":5,\"connection_reuse\":33618,\"connection_wait_count\":33623,\"connection_wait_nanos\":13133428,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"getattr_count\":20670,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7248,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":717,\"wal_checkpoint_nanos\":15421605},\"fuse\":{\"fuse_adapter_attr_hits\":7490,\"fuse_adapter_attr_misses\":5574,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5729,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32059,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":48,\"fuse_dispatch_wait_count\":48,\"fuse_dispatch_wait_nanos\":640838,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13064,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12479,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":603483107,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":638,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":622134,\"fuse_op_getattr_count\":13064,\"fuse_op_getattr_nanos\":86766739,\"fuse_op_lookup_count\":12479,\"fuse_op_lookup_nanos\":168952732,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":744,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":121518705,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":37271856,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":66588162,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1427862086,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":31455804,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17870,\"fuse_read_lane_wait_nanos\":3606586,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45155,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1898317}},\"source\":\"phase-checkpoint-7\"}\n\nSession: git-workload-d85922b0994b49d3b798ece3b36a4cc4\n\nTo resume this session:\n agentfs run --session git-workload-d85922b0994b49d3b798ece3b36a4cc4\n\nTo see what changed:\n agentfs diff git-workload-d85922b0994b49d3b798ece3b36a4cc4\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":1010342344,\"agentfs_batcher_commit_txns\":4816,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1376256,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26122,\"attr_cache_misses\":30824,\"base_fast_inode_invalidations\":15120,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1002,\"connection_create\":5,\"connection_reuse\":33621,\"connection_wait_count\":33626,\"connection_wait_nanos\":13139933,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7490,\"fuse_adapter_attr_misses\":5574,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5729,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32059,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":48,\"fuse_dispatch_wait_count\":48,\"fuse_dispatch_wait_nanos\":640838,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13064,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12479,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":603483107,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":638,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":622134,\"fuse_op_getattr_count\":13064,\"fuse_op_getattr_nanos\":86766739,\"fuse_op_lookup_count\":12479,\"fuse_op_lookup_nanos\":168952732,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":744,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":121518705,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":37271856,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":66588162,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1427862086,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":31455804,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17870,\"fuse_read_lane_wait_nanos\":3606586,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45155,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1899928,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20670,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7248,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":719,\"wal_checkpoint_nanos\":15602480},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":15120,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":1010342344,\"agentfs_batcher_commit_txns\":4816,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1376256,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26122,\"attr_cache_misses\":30824,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1002,\"connection_create\":5,\"connection_reuse\":33621,\"connection_wait_count\":33626,\"connection_wait_nanos\":13139933,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"getattr_count\":20670,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7248,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":719,\"wal_checkpoint_nanos\":15602480},\"fuse\":{\"fuse_adapter_attr_hits\":7490,\"fuse_adapter_attr_misses\":5574,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5729,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32059,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":48,\"fuse_dispatch_wait_count\":48,\"fuse_dispatch_wait_nanos\":640838,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13064,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12479,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":603483107,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":638,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":622134,\"fuse_op_getattr_count\":13064,\"fuse_op_getattr_nanos\":86766739,\"fuse_op_lookup_count\":12479,\"fuse_op_lookup_nanos\":168952732,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":744,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":121518705,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":37271856,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":66588162,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1427862086,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":31455804,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17870,\"fuse_read_lane_wait_nanos\":3606586,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45155,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1899928}},\"source\":\"cli\"}\n", + "stdout_bytes": 19108, + "stdout_tail": "2026-07-04T01:13:42.778944Z INFO agentfs::fuser::session: resolved FUSE dispatch mode: parallel workers=7 queue_capacity=28\n2026-07-04T01:13:42.779224Z INFO agentfs::fuse: advertising FUSE_OVER_IO_URING\n2026-07-04T01:13:42.779445Z INFO agentfs::fuser::uring: starting fuse-over-io_uring queues nr_queues=14 depth=4\n2026-07-04T01:13:46.157006Z WARN agentfs::fuser::request: Request RequestId(60615): Failed to send reply: No such file or directory (os error 2)\n{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.06495013200037647, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work\", \"duration_seconds\": 0.02565217300434597, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work\", \"duration_seconds\": 0.020207842011586763, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work\", \"duration_seconds\": 0.019034737997571938, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.00608031298907008, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work\", \"duration_seconds\": 0.23771682199731003, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work\", \"duration_seconds\": 0.06041515500692185, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/mirror.git\", \"/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/agentfs-base\", \"duration_seconds\": 3.5154075439932058, \"returncode\": 0, \"stderr_bytes\": 2670, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\nUpdating files: 28% (1309/4644)\\nUpdating files: 29% (1347/4644)\\nUpdating files: 30% (1394/4644)\\nUpdating files: 31% (1440/4644)\\nUpdating files: 32% (1487/4644)\\nUpdating files: 33% (1533/4644)\\nUpdating files: 34% (1579/4644)\\nUpdating files: 35% (1626/4644)\\nUpdating files: 36% (1672/4644)\\nUpdating files: 37% (1719/4644)\\nUpdating files: 38% (1765/4644)\\nUpdating files: 39% (1812/4644)\\nUpdating files: 40% (1858/4644)\\nUpdating files: 41% (1905/4644)\\nUpdating files: 42% (1951/4644)\\nUpdating files: 43% (1997/4644)\\nUpdating files: 44% (2044/4644)\\nUpdating files: 45% (2090/4644)\\nUpdating files: 46% (2137/4644)\\nUpdating files: 47% (2183/4644)\\nUpdating files: 48% (2230/4644)\\nUpdating files: 49% (2276/4644)\\nUpdating files: 50% (2322/4644)\\nUpdating files: 51% (2369/4644)\\nUpdating files: 52% (2415/4644)\\nUpdating files: 53% (2462/4644)\\nUpdating files: 54% (2508/4644)\\nUpdating files: 55% (2555/4644)\\nUpdating files: 56% (2601/4644)\\nUpdating files: 57% (2648/4644)\\nUpdating files: 58% (2694/4644)\\nUpdating files: 59% (2740/4644)\\nUpdating files: 60% (2787/4644)\\nUpdating files: 61% (2833/4644)\\nUpdating files: 61% (2862/4644)\\nUpdating files: 62% (2880/4644)\\nUpdating files: 63% (2926/4644)\\nUpdating files: 64% (2973/4644)\\nUpdating files: 65% (3019/4644)\\nUpdating files: 66% (3066/4644)\\nUpdating files: 67% (3112/4644)\\nUpdating files: 68% (3158/4644)\\nUpdating files: 69% (3205/4644)\\nUpdating files: 70% (3251/4644)\\nUpdating files: 71% (3298/4644)\\nUpdating files: 72% (3344/4644)\\nUpdating files: 73% (3391/4644)\\nUpdating files: 74% (3437/4644)\\nUpdating files: 75% (3483/4644)\\nUpdating files: 76% (3530/4644)\\nUpdating files: 77% (3576/4644)\\nUpdating files: 78% (3623/4644)\\nUpdating files: 79% (3669/4644)\\nUpdating files: 80% (3716/4644)\\nUpdating files: 81% (3762/4644)\\nUpdating files: 82% (3809/4644)\\nUpdating files: 83% (3855/4644)\\nUpdating files: 84% (3901/4644)\\nUpdating files: 85% (3948/4644)\\nUpdating files: 86% (3994/4644)\\nUpdating files: 87% (4041/4644)\\nUpdating files: 88% (4087/4644)\\nUpdating files: 89% (4134/4644)\\nUpdating files: 90% (4180/4644)\\nUpdating files: 91% (4227/4644)\\nUpdating files: 92% (4273/4644)\\nUpdating files: 93% (4319/4644)\\nUpdating files: 94% (4366/4644)\\nUpdating files: 94% (4378/4644)\\nUpdating files: 95% (4412/4644)\\nUpdating files: 96% (4459/4644)\\nUpdating files: 97% (4505/4644)\\nUpdating files: 98% (4552/4644)\\nUpdating files: 99% (4598/4644)\\nUpdating files: 100% (4644/4644)\\nUpdating files: 100% (4644/4644), done.\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work\", \"duration_seconds\": 0.05948514700867236, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work\", \"duration_seconds\": 0.1309478760085767, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.06318244199792389, \"clone\": 3.515449283993803, \"diff\": 0.06495013200037647, \"edit\": 0.00608031298907008, \"fsck\": 0.23772984399693087, \"read_search\": 0.010672920994693413, \"status\": 0.19046289600373711}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work\", \"duration_seconds\": 0.007538463003584184, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 4.789309019004577}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.06495013200037647, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work", + "duration_seconds": 0.02565217300434597, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work", + "duration_seconds": 0.020207842011586763, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work", + "duration_seconds": 0.019034737997571938, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.00608031298907008, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work", + "duration_seconds": 0.23771682199731003, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work", + "duration_seconds": 0.06041515500692185, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/mirror.git", + "/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/agentfs-base", + "duration_seconds": 3.5154075439932058, + "returncode": 0, + "stderr_bytes": 2670, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\nUpdating files: 28% (1309/4644)\nUpdating files: 29% (1347/4644)\nUpdating files: 30% (1394/4644)\nUpdating files: 31% (1440/4644)\nUpdating files: 32% (1487/4644)\nUpdating files: 33% (1533/4644)\nUpdating files: 34% (1579/4644)\nUpdating files: 35% (1626/4644)\nUpdating files: 36% (1672/4644)\nUpdating files: 37% (1719/4644)\nUpdating files: 38% (1765/4644)\nUpdating files: 39% (1812/4644)\nUpdating files: 40% (1858/4644)\nUpdating files: 41% (1905/4644)\nUpdating files: 42% (1951/4644)\nUpdating files: 43% (1997/4644)\nUpdating files: 44% (2044/4644)\nUpdating files: 45% (2090/4644)\nUpdating files: 46% (2137/4644)\nUpdating files: 47% (2183/4644)\nUpdating files: 48% (2230/4644)\nUpdating files: 49% (2276/4644)\nUpdating files: 50% (2322/4644)\nUpdating files: 51% (2369/4644)\nUpdating files: 52% (2415/4644)\nUpdating files: 53% (2462/4644)\nUpdating files: 54% (2508/4644)\nUpdating files: 55% (2555/4644)\nUpdating files: 56% (2601/4644)\nUpdating files: 57% (2648/4644)\nUpdating files: 58% (2694/4644)\nUpdating files: 59% (2740/4644)\nUpdating files: 60% (2787/4644)\nUpdating files: 61% (2833/4644)\nUpdating files: 61% (2862/4644)\nUpdating files: 62% (2880/4644)\nUpdating files: 63% (2926/4644)\nUpdating files: 64% (2973/4644)\nUpdating files: 65% (3019/4644)\nUpdating files: 66% (3066/4644)\nUpdating files: 67% (3112/4644)\nUpdating files: 68% (3158/4644)\nUpdating files: 69% (3205/4644)\nUpdating files: 70% (3251/4644)\nUpdating files: 71% (3298/4644)\nUpdating files: 72% (3344/4644)\nUpdating files: 73% (3391/4644)\nUpdating files: 74% (3437/4644)\nUpdating files: 75% (3483/4644)\nUpdating files: 76% (3530/4644)\nUpdating files: 77% (3576/4644)\nUpdating files: 78% (3623/4644)\nUpdating files: 79% (3669/4644)\nUpdating files: 80% (3716/4644)\nUpdating files: 81% (3762/4644)\nUpdating files: 82% (3809/4644)\nUpdating files: 83% (3855/4644)\nUpdating files: 84% (3901/4644)\nUpdating files: 85% (3948/4644)\nUpdating files: 86% (3994/4644)\nUpdating files: 87% (4041/4644)\nUpdating files: 88% (4087/4644)\nUpdating files: 89% (4134/4644)\nUpdating files: 90% (4180/4644)\nUpdating files: 91% (4227/4644)\nUpdating files: 92% (4273/4644)\nUpdating files: 93% (4319/4644)\nUpdating files: 94% (4366/4644)\nUpdating files: 94% (4378/4644)\nUpdating files: 95% (4412/4644)\nUpdating files: 96% (4459/4644)\nUpdating files: 97% (4505/4644)\nUpdating files: 98% (4552/4644)\nUpdating files: 99% (4598/4644)\nUpdating files: 100% (4644/4644)\nUpdating files: 100% (4644/4644), done.\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work", + "duration_seconds": 0.05948514700867236, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work", + "duration_seconds": 0.1309478760085767, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.06318244199792389, + "clone": 3.515449283993803, + "diff": 0.06495013200037647, + "edit": 0.00608031298907008, + "fsck": 0.23772984399693087, + "read_search": 0.010672920994693413, + "status": 0.19046289600373711 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/agentfs-base/work", + "duration_seconds": 0.007538463003584184, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 4.789309019004577 + } + }, + "base_tree": { + "after": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "82e89864e870a8be89c5426bdee7b5a337ae2076d53c71a033173cde841a0a8e", + "symlinks": 0 + }, + "before": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "82e89864e870a8be89c5426bdee7b5a337ae2076d53c71a033173cde841a0a8e", + "symlinks": 0 + }, + "unchanged": true + }, + "benchmark": "phase7-git-workload", + "command": { + "agentfs_prefix": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-d85922b0994b49d3b798ece3b36a4cc4", + "--no-default-allows", + "--" + ], + "argv": [ + "/home/ain3sh/factory/vfs/scripts/validation/git-workload-benchmark.py", + "--output", + "/tmp/vfs-val/misc-a-bench-rebaseline/run3.json.iterations/iter-00.json", + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "workload_argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ] + }, + "correctness": { + "agentfs_backup_verify": true, + "agentfs_base_unchanged": true, + "agentfs_db_inspectable": true, + "agentfs_integrity_require_portable": true, + "agentfs_no_nonempty_sidecars": true, + "agentfs_portable": true, + "agentfs_returncode_zero": true, + "equivalence": { + "agentfs": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + }, + "checked": true, + "equivalent": true, + "native": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + } + }, + "native_returncode_zero": true, + "passed": true, + "performance_passed": false + }, + "database": { + "after": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-8kjmq69z/home/.agentfs/run/git-workload-d85922b0994b49d3b798ece3b36a4cc4/delta.db" + } + ], + "path": "/tmp/agentfs-git-workload-8kjmq69z/home/.agentfs/run/git-workload-d85922b0994b49d3b798ece3b36a4cc4/delta.db", + "total_bytes": 57147392 + }, + "backup": { + "artifacts": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-8kjmq69z/git-workload-backup.db" + } + ], + "path": "/tmp/agentfs-git-workload-8kjmq69z/git-workload-backup.db", + "total_bytes": 57147392 + }, + "inspect": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "path": "/tmp/agentfs-git-workload-8kjmq69z/git-workload-backup.db", + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "backup", + "/tmp/agentfs-git-workload-8kjmq69z/home/.agentfs/run/git-workload-d85922b0994b49d3b798ece3b36a4cc4/delta.db", + "/tmp/agentfs-git-workload-8kjmq69z/git-workload-backup.db", + "--verify" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z", + "duration_seconds": 2.7143653749953955, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 0, + "fuse_readdirplus_mode": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_workers_configured": 0, + "fuse_writeback_cache_enabled": 0 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "fuse": { + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 7355, + "stderr_tail": "{\"counters\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_mode\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_workers_configured\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0,\"fuse_writeback_cache_enabled\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":0,\"fuse_readdirplus_mode\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_workers_configured\":0,\"fuse_writeback_cache_enabled\":0},\"core\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"fuse\":{\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0}},\"source\":\"cli\"}\n", + "stdout_bytes": 241, + "stdout_tail": "Source: /tmp/agentfs-git-workload-8kjmq69z/home/.agentfs/run/git-workload-d85922b0994b49d3b798ece3b36a4cc4/delta.db\nBackup: /tmp/agentfs-git-workload-8kjmq69z/git-workload-backup.db\nCheckpoint: complete\nCopy: complete\nVerification: complete\n", + "timed_out": false + } + }, + "inspect_after": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "integrity": { + "result": { + "checks": [ + { + "detail": "ok", + "name": "pragma.integrity_check", + "ok": true, + "violating_rows": null + }, + { + "detail": "present", + "name": "schema.table.fs_config", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_symlink", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.kv_store", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.tool_calls", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 0.5", + "name": "config.schema_version", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 65536", + "name": "config.chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 16384", + "name": "config.inline_threshold", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.kind_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_has_no_chunks", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunked_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_size_matches_blob", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.non_regular_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_reference_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunk_length_within_chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 1, expected 1", + "name": "namespace.root_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_is_directory", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_target_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_root_inode_has_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_names_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_directory_nlink_matches_dentries", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.directory_nlink_positive", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.rows_reference_symlink_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.inodes_have_rows", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable: no partial-origin rows", + "name": "overlay.portability_status", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable requirement satisfied", + "name": "overlay.require_portable", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_regular", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_sizes_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_paths_absolute", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_references_partial_origin", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_unique", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_index_in_range", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.whiteout_paths_absolute", + "ok": true, + "violating_rows": 0 + } + ], + "database": "/tmp/agentfs-git-workload-8kjmq69z/home/.agentfs/run/git-workload-d85922b0994b49d3b798ece3b36a4cc4/delta.db", + "ok": true, + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true + }, + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "integrity", + "/tmp/agentfs-git-workload-8kjmq69z/home/.agentfs/run/git-workload-d85922b0994b49d3b798ece3b36a4cc4/delta.db", + "--json", + "--require-portable" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z", + "duration_seconds": 2.879678741999669, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 0, + "fuse_readdirplus_mode": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_workers_configured": 0, + "fuse_writeback_cache_enabled": 0 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "fuse": { + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 7355, + "stderr_tail": "{\"counters\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_mode\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_workers_configured\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0,\"fuse_writeback_cache_enabled\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":0,\"fuse_readdirplus_mode\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_workers_configured\":0,\"fuse_writeback_cache_enabled\":0},\"core\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"fuse\":{\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0}},\"source\":\"cli\"}\n", + "stdout_bytes": 6395, + "stdout_tail": "{\n \"database\": \"/tmp/agentfs-git-workload-8kjmq69z/home/.agentfs/run/git-workload-d85922b0994b49d3b798ece3b36a4cc4/delta.db\",\n \"ok\": true,\n \"portable\": true,\n \"origin_backed\": false,\n \"partial_origin_rows\": 0,\n \"checks\": [\n {\n \"name\": \"pragma.integrity_check\",\n \"ok\": true,\n \"detail\": \"ok\",\n \"violating_rows\": null\n },\n {\n \"name\": \"schema.table.fs_config\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_inode\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_dentry\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_data\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_symlink\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.kv_store\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.tool_calls\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.schema_version\",\n \"ok\": true,\n \"detail\": \"found 0.5\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.chunk_size\",\n \"ok\": true,\n \"detail\": \"found 65536\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.inline_threshold\",\n \"ok\": true,\n \"detail\": \"found 16384\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.kind_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_has_no_chunks\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunked_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_size_matches_blob\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.non_regular_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_reference_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunk_length_within_chunk_size\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.root_inode\",\n \"ok\": true,\n \"detail\": \"found 1, expected 1\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_is_directory\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_target_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_root_inode_has_dentry\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_names_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_directory_nlink_matches_dentries\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.directory_nlink_positive\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.rows_reference_symlink_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.inodes_have_rows\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.portability_status\",\n \"ok\": true,\n \"detail\": \"portable: no partial-origin rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.require_portable\",\n \"ok\": true,\n \"detail\": \"portable requirement satisfied\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_regular\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_sizes_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_references_partial_origin\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_unique\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_index_in_range\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.whiteout_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n }\n ]\n}\n", + "timed_out": false + } + }, + "nonempty_sidecars": false + }, + "environment": { + "AGENTFS_BIN": "/home/ain3sh/factory/vfs/target/release/agentfs", + "AGENTFS_PROFILE": "1" + }, + "git_commit": "2b811f03493e861e5a7786592d25ac73b39e1aba", + "kept_temp": false, + "native": { + "run": { + "argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/native", + "duration_seconds": 1.152063450004789, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 15983, + "stdout_tail": "{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.19561760700889863, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/native/work\", \"duration_seconds\": 0.08078276600281242, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/native/work\", \"duration_seconds\": 0.0579187940020347, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/native/work\", \"duration_seconds\": 0.05683751800097525, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.0010122020030394197, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/native/work\", \"duration_seconds\": 0.21747226201114245, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/native/work\", \"duration_seconds\": 0.11604733800049871, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-8kjmq69z/native/mirror.git\", \"/tmp/agentfs-git-workload-8kjmq69z/native/work\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/native\", \"duration_seconds\": 0.4125937500066357, \"returncode\": 0, \"stderr_bytes\": 149, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-8kjmq69z/native/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/native/work\", \"duration_seconds\": 0.05681486199318897, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/native/work\", \"duration_seconds\": 0.06506867999269161, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.11891778999415692, \"clone\": 0.41264774699811824, \"diff\": 0.19561760700889863, \"edit\": 0.0010122020030394197, \"fsck\": 0.21748976400704123, \"read_search\": 0.009386195000843145, \"status\": 0.12191534000157844}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-8kjmq69z/native/work\", \"duration_seconds\": 0.004784304997883737, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 1.07730795101088}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.19561760700889863, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/native/work", + "duration_seconds": 0.08078276600281242, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/native/work", + "duration_seconds": 0.0579187940020347, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/native/work", + "duration_seconds": 0.05683751800097525, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.0010122020030394197, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/native/work", + "duration_seconds": 0.21747226201114245, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/native/work", + "duration_seconds": 0.11604733800049871, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-8kjmq69z/native/mirror.git", + "/tmp/agentfs-git-workload-8kjmq69z/native/work" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/native", + "duration_seconds": 0.4125937500066357, + "returncode": 0, + "stderr_bytes": 149, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-8kjmq69z/native/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/native/work", + "duration_seconds": 0.05681486199318897, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/native/work", + "duration_seconds": 0.06506867999269161, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.11891778999415692, + "clone": 0.41264774699811824, + "diff": 0.19561760700889863, + "edit": 0.0010122020030394197, + "fsck": 0.21748976400704123, + "read_search": 0.009386195000843145, + "status": 0.12191534000157844 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-8kjmq69z/native/work", + "duration_seconds": 0.004784304997883737, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 1.07730795101088 + } + }, + "parameters": { + "edit_files": 8, + "fixture_dirs": 8, + "fixture_file_size_bytes": 1024, + "fixture_files": 96, + "read_bytes": 4096, + "read_files": 64, + "search_token": "AGENTFS_TOKEN", + "skip_fsck": false, + "timeout_seconds": 180.0 + }, + "schema_version": 1, + "source": { + "kind": "source", + "mirror_head": "7d47056ea42636271ac020b86347fbbef49490aa", + "path": "/home/ain3sh/factory/vfs/.agents/benchmarks/fixtures/codex" + }, + "summary": { + "agentfs_base_unchanged": true, + "agentfs_seconds": 4.789309019004577, + "all_equivalent": true, + "correctness_passed": true, + "native_seconds": 1.07730795101088, + "passed": true, + "performance_passed": false, + "phase_ratios": { + "checkout": { + "agentfs_seconds": 0.06318244199792389, + "native_seconds": 0.11891778999415692, + "ratio": 0.5313119424858836 + }, + "clone": { + "agentfs_seconds": 3.515449283993803, + "native_seconds": 0.41264774699811824, + "ratio": 8.51924991610298 + }, + "diff": { + "agentfs_seconds": 0.06495013200037647, + "native_seconds": 0.19561760700889863, + "ratio": 0.3320260021247571 + }, + "edit": { + "agentfs_seconds": 0.00608031298907008, + "native_seconds": 0.0010122020030394197, + "ratio": 6.0070153692763295 + }, + "fsck": { + "agentfs_seconds": 0.23772984399693087, + "native_seconds": 0.21748976400704123, + "ratio": 1.0930622187315187 + }, + "read_search": { + "agentfs_seconds": 0.010672920994693413, + "native_seconds": 0.009386195000843145, + "ratio": 1.1370870724222844 + }, + "status": { + "agentfs_seconds": 0.19046289600373711, + "native_seconds": 0.12191534000157844, + "ratio": 1.562255381490723 + } + }, + "ratio": 4.445626725868477, + "threshold_failures": [ + { + "agentfs_seconds": 3.515449283993803, + "native_seconds": 0.41264774699811824, + "phase": "clone", + "ratio": 8.51924991610298 + }, + { + "agentfs_seconds": 0.00608031298907008, + "native_seconds": 0.0010122020030394197, + "phase": "edit", + "ratio": 6.0070153692763295 + } + ] + }, + "temp_dir": "/tmp/agentfs-git-workload-8kjmq69z" +} diff --git a/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-01.json b/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-01.json new file mode 100644 index 00000000..ad6f65cf --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-01.json @@ -0,0 +1,8686 @@ +{ + "agentfs": { + "bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "db_path": "/tmp/agentfs-git-workload-qcdrpuw6/home/.agentfs/run/git-workload-14fbea9ebb104bf2b3f13d669d6de464/delta.db", + "per_phase_counters": { + "checkpoint_count": 7, + "label_count": 7, + "labels_aligned": true, + "phases": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 36, + "agentfs_batcher_commit_latency_ns_total": 793963655, + "agentfs_batcher_commit_txns": 4780, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25920, + "attr_cache_misses": 29058, + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create": 2, + "connection_reuse": 31213, + "connection_wait_count": 31215, + "connection_wait_nanos": 9511223, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 92, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5281, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21782, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 35, + "fuse_dispatch_wait_count": 35, + "fuse_dispatch_wait_nanos": 565645, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5026, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11698, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 470554097, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 961875, + "fuse_op_getattr_count": 5026, + "fuse_op_getattr_nanos": 50990564, + "fuse_op_lookup_count": 11698, + "fuse_op_lookup_nanos": 124727987, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 95364166, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 28440514, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2483112, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1126358331, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 23708287, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16156, + "fuse_read_lane_wait_nanos": 2438969, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32006, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1834580, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19338, + "lookup_base_count": 23, + "lookup_count": 40290, + "lookup_delta_count": 7192, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6776, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32905, + "path_resolution_count": 7119, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1946817 + }, + "phase": "clone", + "seq": 1 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 2, + "agentfs_batcher_commit_latency_ns_total": 3321399, + "agentfs_batcher_commit_txns": 10, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 5, + "agentfs_batcher_drains_timer": 2, + "agentfs_batcher_enqueues": 9, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 14, + "attr_cache_hits": 242, + "attr_cache_misses": 1773, + "base_fast_inode_invalidations": 51, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 12, + "connection_create": 6, + "connection_reuse": 626, + "connection_wait_count": 632, + "connection_wait_nanos": 285279, + "dentry_cache_hits": 73, + "dentry_cache_misses": 56, + "fuse_adapter_attr_hits": 4578, + "fuse_adapter_attr_misses": 695, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 35, + "fuse_adapter_inval_entry_notifications": 10, + "fuse_adapter_inval_inode_notifications": 28, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 12, + "fuse_adapter_negative_misses": 35, + "fuse_callback_count": 5343, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 9, + "fuse_dispatch_wait_count": 9, + "fuse_dispatch_wait_nanos": 57233, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 694731, + "fuse_flush_count": 9, + "fuse_flush_ranges": 9, + "fuse_getattr_count": 5273, + "fuse_ino_file_resolutions": 3, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 49, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 9, + "fuse_op_create_nanos": 1953410, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 9, + "fuse_op_forget_nanos": 70431, + "fuse_op_getattr_count": 5273, + "fuse_op_getattr_nanos": 21127118, + "fuse_op_lookup_count": 49, + "fuse_op_lookup_nanos": 606794, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 15, + "fuse_op_other_nanos": 2796320, + "fuse_op_read_count": 8, + "fuse_op_read_nanos": 1154129, + "fuse_op_readdirplus_count": 4, + "fuse_op_readdirplus_nanos": 1231941, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 14, + "fuse_op_setattr_nanos": 4135047, + "fuse_op_write_count": 9, + "fuse_op_write_nanos": 189495, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 8, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 760, + "fuse_read_lane_wait_nanos": 143011, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 4, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5381, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 591393, + "fuse_write_count": 9, + "fuse_write_lane_wait_count": 42, + "fuse_write_lane_wait_nanos": 40187, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 1418, + "lookup_base_count": 0, + "lookup_count": 164, + "lookup_delta_count": 60, + "lookup_whiteout_count": 0, + "negative_cache_hits": 33, + "negative_cache_invalidations": 26, + "negative_cache_misses": 148, + "negative_lookup_count": 86, + "path_cache_hits": 73, + "path_cache_misses": 56, + "path_component_count": 124, + "path_resolution_count": 45, + "readdir_count": 0, + "readdir_plus_count": 4, + "wal_checkpoint_count": 2, + "wal_checkpoint_nanos": 880000 + }, + "phase": "checkout", + "seq": 2 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 4, + "agentfs_batcher_commit_latency_ns_total": 2983772, + "agentfs_batcher_commit_txns": 4, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 2, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 6, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 4, + "attr_cache_hits": 19, + "attr_cache_misses": 33, + "base_fast_inode_invalidations": 24, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 20, + "chunk_read_queries": 12, + "chunk_write_chunks": 20, + "connection_create": 0, + "connection_reuse": 1704, + "connection_wait_count": 1704, + "connection_wait_nanos": 455379, + "dentry_cache_hits": 19, + "dentry_cache_misses": 570, + "fuse_adapter_attr_hits": 1376, + "fuse_adapter_attr_misses": 6, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 289, + "fuse_adapter_inval_entry_notifications": 4, + "fuse_adapter_inval_inode_notifications": 8, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 2, + "fuse_adapter_negative_misses": 289, + "fuse_callback_count": 3078, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 3, + "fuse_dispatch_wait_count": 3, + "fuse_dispatch_wait_nanos": 32842, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 1181458, + "fuse_flush_count": 6, + "fuse_flush_ranges": 6, + "fuse_getattr_count": 1382, + "fuse_ino_file_resolutions": 2, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 293, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 2, + "fuse_op_create_nanos": 948176, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 3, + "fuse_op_forget_nanos": 27536, + "fuse_op_getattr_count": 1382, + "fuse_op_getattr_nanos": 1390508, + "fuse_op_lookup_count": 293, + "fuse_op_lookup_nanos": 5278086, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 2762, + "fuse_op_other_nanos": 2035821, + "fuse_op_read_count": 12, + "fuse_op_read_nanos": 3899245, + "fuse_op_readdirplus_count": 1381, + "fuse_op_readdirplus_nanos": 66460321, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 6, + "fuse_op_setattr_nanos": 5047795, + "fuse_op_write_count": 10, + "fuse_op_write_nanos": 1316213, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 12, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 995, + "fuse_read_lane_wait_nanos": 125561, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1381, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5848, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 1181458, + "fuse_write_count": 10, + "fuse_write_lane_wait_count": 14, + "fuse_write_lane_wait_nanos": 2278, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 24, + "lookup_base_count": 0, + "lookup_count": 598, + "lookup_delta_count": 295, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6, + "negative_cache_invalidations": 4, + "negative_cache_misses": 873, + "negative_lookup_count": 578, + "path_cache_hits": 19, + "path_cache_misses": 570, + "path_component_count": 4606, + "path_resolution_count": 977, + "readdir_count": 0, + "readdir_plus_count": 1376, + "wal_checkpoint_count": 688, + "wal_checkpoint_nanos": 10654237 + }, + "phase": "status", + "seq": 3 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 1, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 8, + "connection_wait_count": 8, + "connection_wait_nanos": 5160, + "dentry_cache_hits": 1, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 69, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 1, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 1, + "fuse_callback_count": 76, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 69, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 1, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 69, + "fuse_op_getattr_nanos": 63234, + "fuse_op_lookup_count": 1, + "fuse_op_lookup_nanos": 26753, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 6, + "fuse_op_read_nanos": 906227, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 6, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 2, + "fuse_read_lane_wait_nanos": 788, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 76, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 2, + "lookup_delta_count": 1, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 2, + "negative_lookup_count": 0, + "path_cache_hits": 1, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "read_search", + "seq": 4 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 1149137, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 8, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 8, + "attr_cache_hits": 0, + "attr_cache_misses": 24, + "base_fast_inode_invalidations": 16, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 32, + "connection_wait_count": 32, + "connection_wait_nanos": 10385, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 16, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 24, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 6398, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 16, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 16, + "fuse_op_getattr_nanos": 19174, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 8, + "fuse_op_other_nanos": 883763, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 8, + "fuse_op_setattr_nanos": 2036509, + "fuse_op_write_count": 8, + "fuse_op_write_nanos": 460863, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 8, + "fuse_read_lane_wait_nanos": 2720, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 40, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 6398, + "fuse_write_count": 8, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 1519, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 16, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 8, + "wal_checkpoint_nanos": 784875 + }, + "phase": "edit", + "seq": 5 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 1864, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 68, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 1932, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 1864, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 68, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 1864, + "fuse_op_getattr_nanos": 1057287, + "fuse_op_lookup_count": 68, + "fuse_op_lookup_nanos": 88141, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 12, + "fuse_op_other_nanos": 4515, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 1944, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 68, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "diff", + "seq": 6 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 3, + "attr_cache_misses": 2, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 1, + "chunk_read_queries": 1, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 24, + "connection_wait_count": 24, + "connection_wait_nanos": 15412, + "dentry_cache_hits": 4, + "dentry_cache_misses": 2, + "fuse_adapter_attr_hits": 44, + "fuse_adapter_attr_misses": 1, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 4, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 247, + "fuse_adapter_negative_misses": 4, + "fuse_callback_count": 316, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 45, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 251, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 45, + "fuse_op_getattr_nanos": 80152, + "fuse_op_lookup_count": 251, + "fuse_op_lookup_nanos": 335907, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 36, + "fuse_op_other_nanos": 11476, + "fuse_op_read_count": 2, + "fuse_op_read_nanos": 328634, + "fuse_op_readdirplus_count": 18, + "fuse_op_readdirplus_nanos": 1007982, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 2, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 15, + "fuse_read_lane_wait_nanos": 3704, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 18, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 352, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 2, + "lookup_base_count": 0, + "lookup_count": 8, + "lookup_delta_count": 4, + "lookup_whiteout_count": 0, + "negative_cache_hits": 247, + "negative_cache_invalidations": 0, + "negative_cache_misses": 9, + "negative_lookup_count": 2, + "path_cache_hits": 4, + "path_cache_misses": 2, + "path_component_count": 42, + "path_resolution_count": 10, + "readdir_count": 0, + "readdir_plus_count": 18, + "wal_checkpoint_count": 9, + "wal_checkpoint_nanos": 304087 + }, + "phase": "fsck", + "seq": 7 + } + ] + }, + "profile_counters": { + "last_by_source": { + "cli": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26185, + "attr_cache_misses": 30890, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33610, + "connection_wait_count": 33618, + "connection_wait_nanos": 10288353, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8039, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5610, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32551, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13675, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12360, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13675, + "fuse_op_getattr_nanos": 74728037, + "fuse_op_lookup_count": 12360, + "fuse_op_lookup_nanos": 131063668, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 101096061, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34728749, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 71183356, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17936, + "fuse_read_lane_wait_nanos": 2714753, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45647, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1879845, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20798, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7130, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 14693733 + }, + "phase-checkpoint-1": { + "agentfs_batcher_coalesced_ranges": 36, + "agentfs_batcher_commit_latency_ns_total": 793963655, + "agentfs_batcher_commit_txns": 4780, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25920, + "attr_cache_misses": 29058, + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create": 2, + "connection_reuse": 31213, + "connection_wait_count": 31215, + "connection_wait_nanos": 9511223, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 92, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5281, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21782, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 35, + "fuse_dispatch_wait_count": 35, + "fuse_dispatch_wait_nanos": 565645, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5026, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11698, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 470554097, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 961875, + "fuse_op_getattr_count": 5026, + "fuse_op_getattr_nanos": 50990564, + "fuse_op_lookup_count": 11698, + "fuse_op_lookup_nanos": 124727987, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 95364166, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 28440514, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2483112, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1126358331, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 23708287, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16156, + "fuse_read_lane_wait_nanos": 2438969, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32006, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1834580, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19338, + "lookup_base_count": 23, + "lookup_count": 40290, + "lookup_delta_count": 7192, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6776, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32905, + "path_resolution_count": 7119, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1946817 + }, + "phase-checkpoint-2": { + "agentfs_batcher_coalesced_ranges": 38, + "agentfs_batcher_commit_latency_ns_total": 797285054, + "agentfs_batcher_commit_txns": 4790, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26162, + "attr_cache_misses": 30831, + "base_fast_inode_invalidations": 15082, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 8, + "connection_reuse": 31839, + "connection_wait_count": 31847, + "connection_wait_nanos": 9796502, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4670, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5293, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27125, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 622878, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10299, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11747, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 472507507, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 1032306, + "fuse_op_getattr_count": 10299, + "fuse_op_getattr_nanos": 72117682, + "fuse_op_lookup_count": 11747, + "fuse_op_lookup_nanos": 125334781, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 98160486, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 29594643, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3715053, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1130493378, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 23897782, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 16916, + "fuse_read_lane_wait_nanos": 2581980, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37387, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1874767, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20756, + "lookup_base_count": 23, + "lookup_count": 40454, + "lookup_delta_count": 7252, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6809, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33029, + "path_resolution_count": 7164, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2826817 + }, + "phase-checkpoint-3": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 800268826, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26181, + "attr_cache_misses": 30864, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33543, + "connection_wait_count": 33551, + "connection_wait_nanos": 10251881, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6046, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30203, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11681, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12040, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11681, + "fuse_op_getattr_nanos": 73508190, + "fuse_op_lookup_count": 12040, + "fuse_op_lookup_nanos": 130612867, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 100196307, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 33493888, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1135541173, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 25213995, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17911, + "fuse_read_lane_wait_nanos": 2707541, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43235, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1877045, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13481054 + }, + "phase-checkpoint-4": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 800268826, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26182, + "attr_cache_misses": 30864, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33551, + "connection_wait_count": 33559, + "connection_wait_nanos": 10257041, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6115, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30279, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11750, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12041, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11750, + "fuse_op_getattr_nanos": 73571424, + "fuse_op_lookup_count": 12041, + "fuse_op_lookup_nanos": 130639620, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 100196307, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1135541173, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 25213995, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17913, + "fuse_read_lane_wait_nanos": 2708329, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43311, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1877045, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13481054 + }, + "phase-checkpoint-5": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26182, + "attr_cache_misses": 30888, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33583, + "connection_wait_count": 33591, + "connection_wait_nanos": 10267426, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6131, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30303, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11766, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12041, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11766, + "fuse_op_getattr_nanos": 73590598, + "fuse_op_lookup_count": 12041, + "fuse_op_lookup_nanos": 130639620, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 101080070, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17921, + "fuse_read_lane_wait_nanos": 2711049, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43351, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20796, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14265929 + }, + "phase-checkpoint-6": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26182, + "attr_cache_misses": 30888, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33583, + "connection_wait_count": 33591, + "connection_wait_nanos": 10267426, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7995, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5363, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32235, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13630, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12109, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13630, + "fuse_op_getattr_nanos": 74647885, + "fuse_op_lookup_count": 12109, + "fuse_op_lookup_nanos": 130727761, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 101084585, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17921, + "fuse_read_lane_wait_nanos": 2711049, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45295, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20796, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6883, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14265929 + }, + "phase-checkpoint-7": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26185, + "attr_cache_misses": 30890, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33607, + "connection_wait_count": 33615, + "connection_wait_nanos": 10282838, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8039, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5610, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32551, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13675, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12360, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13675, + "fuse_op_getattr_nanos": 74728037, + "fuse_op_lookup_count": 12360, + "fuse_op_lookup_nanos": 131063668, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 101096061, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34728749, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 71183356, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17936, + "fuse_read_lane_wait_nanos": 2714753, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45647, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20798, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7130, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 14570016 + } + }, + "max_counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26185, + "attr_cache_misses": 30890, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33610, + "connection_wait_count": 33618, + "connection_wait_nanos": 10288353, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8039, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5610, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32551, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13675, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12360, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13675, + "fuse_op_getattr_nanos": 74728037, + "fuse_op_lookup_count": 12360, + "fuse_op_lookup_nanos": 131063668, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 101096061, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34728749, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 71183356, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17936, + "fuse_read_lane_wait_nanos": 2714753, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45647, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1879845, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20798, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7130, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 14693733 + }, + "summary_count": 8 + }, + "profile_enabled": true, + "profile_summary_count": 8, + "session": "git-workload-14fbea9ebb104bf2b3f13d669d6de464" + }, + "agentfs_overlay": { + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 36, + "agentfs_batcher_commit_latency_ns_total": 793963655, + "agentfs_batcher_commit_txns": 4780, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25920, + "attr_cache_misses": 29058, + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create": 2, + "connection_reuse": 31213, + "connection_wait_count": 31215, + "connection_wait_nanos": 9511223, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 92, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5281, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21782, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 35, + "fuse_dispatch_wait_count": 35, + "fuse_dispatch_wait_nanos": 565645, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5026, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11698, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 470554097, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 961875, + "fuse_op_getattr_count": 5026, + "fuse_op_getattr_nanos": 50990564, + "fuse_op_lookup_count": 11698, + "fuse_op_lookup_nanos": 124727987, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 95364166, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 28440514, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2483112, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1126358331, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 23708287, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16156, + "fuse_read_lane_wait_nanos": 2438969, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32006, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1834580, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19338, + "lookup_base_count": 23, + "lookup_count": 40290, + "lookup_delta_count": 7192, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6776, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32905, + "path_resolution_count": 7119, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1946817 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 36, + "agentfs_batcher_commit_latency_ns_total": 793963655, + "agentfs_batcher_commit_txns": 4780, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25920, + "attr_cache_misses": 29058, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create": 2, + "connection_reuse": 31213, + "connection_wait_count": 31215, + "connection_wait_nanos": 9511223, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "getattr_count": 19338, + "lookup_base_count": 23, + "lookup_count": 40290, + "lookup_delta_count": 7192, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6776, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32905, + "path_resolution_count": 7119, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1946817 + }, + "fuse": { + "fuse_adapter_attr_hits": 92, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5281, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21782, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 35, + "fuse_dispatch_wait_count": 35, + "fuse_dispatch_wait_nanos": 565645, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5026, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11698, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 470554097, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 961875, + "fuse_op_getattr_count": 5026, + "fuse_op_getattr_nanos": 50990564, + "fuse_op_lookup_count": 11698, + "fuse_op_lookup_nanos": 124727987, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 95364166, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 28440514, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2483112, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1126358331, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 23708287, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16156, + "fuse_read_lane_wait_nanos": 2438969, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 32006, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1834580 + } + }, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 38, + "agentfs_batcher_commit_latency_ns_total": 797285054, + "agentfs_batcher_commit_txns": 4790, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26162, + "attr_cache_misses": 30831, + "base_fast_inode_invalidations": 15082, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 8, + "connection_reuse": 31839, + "connection_wait_count": 31847, + "connection_wait_nanos": 9796502, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4670, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5293, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27125, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 622878, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10299, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11747, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 472507507, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 1032306, + "fuse_op_getattr_count": 10299, + "fuse_op_getattr_nanos": 72117682, + "fuse_op_lookup_count": 11747, + "fuse_op_lookup_nanos": 125334781, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 98160486, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 29594643, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3715053, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1130493378, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 23897782, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 16916, + "fuse_read_lane_wait_nanos": 2581980, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37387, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1874767, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20756, + "lookup_base_count": 23, + "lookup_count": 40454, + "lookup_delta_count": 7252, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6809, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33029, + "path_resolution_count": 7164, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2826817 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15082, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 38, + "agentfs_batcher_commit_latency_ns_total": 797285054, + "agentfs_batcher_commit_txns": 4790, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26162, + "attr_cache_misses": 30831, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 8, + "connection_reuse": 31839, + "connection_wait_count": 31847, + "connection_wait_nanos": 9796502, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "getattr_count": 20756, + "lookup_base_count": 23, + "lookup_count": 40454, + "lookup_delta_count": 7252, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6809, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33029, + "path_resolution_count": 7164, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2826817 + }, + "fuse": { + "fuse_adapter_attr_hits": 4670, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5293, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27125, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 622878, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10299, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11747, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 472507507, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 1032306, + "fuse_op_getattr_count": 10299, + "fuse_op_getattr_nanos": 72117682, + "fuse_op_lookup_count": 11747, + "fuse_op_lookup_nanos": 125334781, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 98160486, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 29594643, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3715053, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1130493378, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 23897782, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 16916, + "fuse_read_lane_wait_nanos": 2581980, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 37387, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1874767 + } + }, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 800268826, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26181, + "attr_cache_misses": 30864, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33543, + "connection_wait_count": 33551, + "connection_wait_nanos": 10251881, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6046, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30203, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11681, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12040, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11681, + "fuse_op_getattr_nanos": 73508190, + "fuse_op_lookup_count": 12040, + "fuse_op_lookup_nanos": 130612867, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 100196307, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 33493888, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1135541173, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 25213995, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17911, + "fuse_read_lane_wait_nanos": 2707541, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43235, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1877045, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13481054 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 800268826, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26181, + "attr_cache_misses": 30864, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33543, + "connection_wait_count": 33551, + "connection_wait_nanos": 10251881, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13481054 + }, + "fuse": { + "fuse_adapter_attr_hits": 6046, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30203, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11681, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12040, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11681, + "fuse_op_getattr_nanos": 73508190, + "fuse_op_lookup_count": 12040, + "fuse_op_lookup_nanos": 130612867, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 100196307, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 33493888, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1135541173, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 25213995, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17911, + "fuse_read_lane_wait_nanos": 2707541, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43235, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1877045 + } + }, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 800268826, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26182, + "attr_cache_misses": 30864, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33551, + "connection_wait_count": 33559, + "connection_wait_nanos": 10257041, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6115, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30279, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11750, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12041, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11750, + "fuse_op_getattr_nanos": 73571424, + "fuse_op_lookup_count": 12041, + "fuse_op_lookup_nanos": 130639620, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 100196307, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1135541173, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 25213995, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17913, + "fuse_read_lane_wait_nanos": 2708329, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43311, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1877045, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13481054 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 800268826, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26182, + "attr_cache_misses": 30864, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33551, + "connection_wait_count": 33559, + "connection_wait_nanos": 10257041, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13481054 + }, + "fuse": { + "fuse_adapter_attr_hits": 6115, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30279, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11750, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12041, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11750, + "fuse_op_getattr_nanos": 73571424, + "fuse_op_lookup_count": 12041, + "fuse_op_lookup_nanos": 130639620, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 100196307, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1135541173, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 25213995, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17913, + "fuse_read_lane_wait_nanos": 2708329, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43311, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1877045 + } + }, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26182, + "attr_cache_misses": 30888, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33583, + "connection_wait_count": 33591, + "connection_wait_nanos": 10267426, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6131, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30303, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11766, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12041, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11766, + "fuse_op_getattr_nanos": 73590598, + "fuse_op_lookup_count": 12041, + "fuse_op_lookup_nanos": 130639620, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 101080070, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17921, + "fuse_read_lane_wait_nanos": 2711049, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43351, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20796, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14265929 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26182, + "attr_cache_misses": 30888, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33583, + "connection_wait_count": 33591, + "connection_wait_nanos": 10267426, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "getattr_count": 20796, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14265929 + }, + "fuse": { + "fuse_adapter_attr_hits": 6131, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30303, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11766, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12041, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11766, + "fuse_op_getattr_nanos": 73590598, + "fuse_op_lookup_count": 12041, + "fuse_op_lookup_nanos": 130639620, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 101080070, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17921, + "fuse_read_lane_wait_nanos": 2711049, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43351, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564 + } + }, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26182, + "attr_cache_misses": 30888, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33583, + "connection_wait_count": 33591, + "connection_wait_nanos": 10267426, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7995, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5363, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32235, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13630, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12109, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13630, + "fuse_op_getattr_nanos": 74647885, + "fuse_op_lookup_count": 12109, + "fuse_op_lookup_nanos": 130727761, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 101084585, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17921, + "fuse_read_lane_wait_nanos": 2711049, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45295, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20796, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6883, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14265929 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26182, + "attr_cache_misses": 30888, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33583, + "connection_wait_count": 33591, + "connection_wait_nanos": 10267426, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "getattr_count": 20796, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6883, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14265929 + }, + "fuse": { + "fuse_adapter_attr_hits": 7995, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5363, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32235, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13630, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12109, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13630, + "fuse_op_getattr_nanos": 74647885, + "fuse_op_lookup_count": 12109, + "fuse_op_lookup_nanos": 130727761, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 101084585, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17921, + "fuse_read_lane_wait_nanos": 2711049, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45295, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564 + } + }, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26185, + "attr_cache_misses": 30890, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33607, + "connection_wait_count": 33615, + "connection_wait_nanos": 10282838, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8039, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5610, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32551, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13675, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12360, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13675, + "fuse_op_getattr_nanos": 74728037, + "fuse_op_lookup_count": 12360, + "fuse_op_lookup_nanos": 131063668, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 101096061, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34728749, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 71183356, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17936, + "fuse_read_lane_wait_nanos": 2714753, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45647, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20798, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7130, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 14570016 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26185, + "attr_cache_misses": 30890, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33607, + "connection_wait_count": 33615, + "connection_wait_nanos": 10282838, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "getattr_count": 20798, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7130, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 14570016 + }, + "fuse": { + "fuse_adapter_attr_hits": 8039, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5610, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32551, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13675, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12360, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13675, + "fuse_op_getattr_nanos": 74728037, + "fuse_op_lookup_count": 12360, + "fuse_op_lookup_nanos": 131063668, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 101096061, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34728749, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 71183356, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17936, + "fuse_read_lane_wait_nanos": 2714753, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45647, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564 + } + }, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26185, + "attr_cache_misses": 30890, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33610, + "connection_wait_count": 33618, + "connection_wait_nanos": 10288353, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8039, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5610, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32551, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13675, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12360, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13675, + "fuse_op_getattr_nanos": 74728037, + "fuse_op_lookup_count": 12360, + "fuse_op_lookup_nanos": 131063668, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 101096061, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34728749, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 71183356, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17936, + "fuse_read_lane_wait_nanos": 2714753, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45647, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1879845, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20798, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7130, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 14693733 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26185, + "attr_cache_misses": 30890, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33610, + "connection_wait_count": 33618, + "connection_wait_nanos": 10288353, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "getattr_count": 20798, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7130, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 14693733 + }, + "fuse": { + "fuse_adapter_attr_hits": 8039, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5610, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32551, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13675, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12360, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13675, + "fuse_op_getattr_nanos": 74728037, + "fuse_op_lookup_count": 12360, + "fuse_op_lookup_nanos": 131063668, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 101096061, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34728749, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 71183356, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17936, + "fuse_read_lane_wait_nanos": 2714753, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45647, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1879845 + } + }, + "source": "cli" + } + ], + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-14fbea9ebb104bf2b3f13d669d6de464", + "--no-default-allows", + "--", + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base", + "duration_seconds": 4.135514198002056, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 36, + "agentfs_batcher_commit_latency_ns_total": 793963655, + "agentfs_batcher_commit_txns": 4780, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25920, + "attr_cache_misses": 29058, + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create": 2, + "connection_reuse": 31213, + "connection_wait_count": 31215, + "connection_wait_nanos": 9511223, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 92, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5281, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21782, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 35, + "fuse_dispatch_wait_count": 35, + "fuse_dispatch_wait_nanos": 565645, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5026, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11698, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 470554097, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 961875, + "fuse_op_getattr_count": 5026, + "fuse_op_getattr_nanos": 50990564, + "fuse_op_lookup_count": 11698, + "fuse_op_lookup_nanos": 124727987, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 95364166, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 28440514, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2483112, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1126358331, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 23708287, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16156, + "fuse_read_lane_wait_nanos": 2438969, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32006, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1834580, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19338, + "lookup_base_count": 23, + "lookup_count": 40290, + "lookup_delta_count": 7192, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6776, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32905, + "path_resolution_count": 7119, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1946817 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 36, + "agentfs_batcher_commit_latency_ns_total": 793963655, + "agentfs_batcher_commit_txns": 4780, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25920, + "attr_cache_misses": 29058, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create": 2, + "connection_reuse": 31213, + "connection_wait_count": 31215, + "connection_wait_nanos": 9511223, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "getattr_count": 19338, + "lookup_base_count": 23, + "lookup_count": 40290, + "lookup_delta_count": 7192, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6776, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32905, + "path_resolution_count": 7119, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1946817 + }, + "fuse": { + "fuse_adapter_attr_hits": 92, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5281, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21782, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 35, + "fuse_dispatch_wait_count": 35, + "fuse_dispatch_wait_nanos": 565645, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5026, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11698, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 470554097, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 961875, + "fuse_op_getattr_count": 5026, + "fuse_op_getattr_nanos": 50990564, + "fuse_op_lookup_count": 11698, + "fuse_op_lookup_nanos": 124727987, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 95364166, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 28440514, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2483112, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1126358331, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 23708287, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16156, + "fuse_read_lane_wait_nanos": 2438969, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 32006, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1834580 + } + }, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 38, + "agentfs_batcher_commit_latency_ns_total": 797285054, + "agentfs_batcher_commit_txns": 4790, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26162, + "attr_cache_misses": 30831, + "base_fast_inode_invalidations": 15082, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 8, + "connection_reuse": 31839, + "connection_wait_count": 31847, + "connection_wait_nanos": 9796502, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4670, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5293, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27125, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 622878, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10299, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11747, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 472507507, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 1032306, + "fuse_op_getattr_count": 10299, + "fuse_op_getattr_nanos": 72117682, + "fuse_op_lookup_count": 11747, + "fuse_op_lookup_nanos": 125334781, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 98160486, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 29594643, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3715053, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1130493378, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 23897782, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 16916, + "fuse_read_lane_wait_nanos": 2581980, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37387, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1874767, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20756, + "lookup_base_count": 23, + "lookup_count": 40454, + "lookup_delta_count": 7252, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6809, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33029, + "path_resolution_count": 7164, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2826817 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15082, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 38, + "agentfs_batcher_commit_latency_ns_total": 797285054, + "agentfs_batcher_commit_txns": 4790, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26162, + "attr_cache_misses": 30831, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 8, + "connection_reuse": 31839, + "connection_wait_count": 31847, + "connection_wait_nanos": 9796502, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "getattr_count": 20756, + "lookup_base_count": 23, + "lookup_count": 40454, + "lookup_delta_count": 7252, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6809, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33029, + "path_resolution_count": 7164, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2826817 + }, + "fuse": { + "fuse_adapter_attr_hits": 4670, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5293, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27125, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 622878, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10299, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11747, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 472507507, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 1032306, + "fuse_op_getattr_count": 10299, + "fuse_op_getattr_nanos": 72117682, + "fuse_op_lookup_count": 11747, + "fuse_op_lookup_nanos": 125334781, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 98160486, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 29594643, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3715053, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1130493378, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 23897782, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 16916, + "fuse_read_lane_wait_nanos": 2581980, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 37387, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1874767 + } + }, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 800268826, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26181, + "attr_cache_misses": 30864, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33543, + "connection_wait_count": 33551, + "connection_wait_nanos": 10251881, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6046, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30203, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11681, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12040, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11681, + "fuse_op_getattr_nanos": 73508190, + "fuse_op_lookup_count": 12040, + "fuse_op_lookup_nanos": 130612867, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 100196307, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 33493888, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1135541173, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 25213995, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17911, + "fuse_read_lane_wait_nanos": 2707541, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43235, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1877045, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13481054 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 800268826, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26181, + "attr_cache_misses": 30864, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33543, + "connection_wait_count": 33551, + "connection_wait_nanos": 10251881, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13481054 + }, + "fuse": { + "fuse_adapter_attr_hits": 6046, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30203, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11681, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12040, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11681, + "fuse_op_getattr_nanos": 73508190, + "fuse_op_lookup_count": 12040, + "fuse_op_lookup_nanos": 130612867, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 100196307, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 33493888, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1135541173, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 25213995, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17911, + "fuse_read_lane_wait_nanos": 2707541, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43235, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1877045 + } + }, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 800268826, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26182, + "attr_cache_misses": 30864, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33551, + "connection_wait_count": 33559, + "connection_wait_nanos": 10257041, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6115, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30279, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11750, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12041, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11750, + "fuse_op_getattr_nanos": 73571424, + "fuse_op_lookup_count": 12041, + "fuse_op_lookup_nanos": 130639620, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 100196307, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1135541173, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 25213995, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17913, + "fuse_read_lane_wait_nanos": 2708329, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43311, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1877045, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13481054 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 800268826, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26182, + "attr_cache_misses": 30864, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33551, + "connection_wait_count": 33559, + "connection_wait_nanos": 10257041, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13481054 + }, + "fuse": { + "fuse_adapter_attr_hits": 6115, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30279, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11750, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12041, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11750, + "fuse_op_getattr_nanos": 73571424, + "fuse_op_lookup_count": 12041, + "fuse_op_lookup_nanos": 130639620, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 100196307, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1135541173, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 25213995, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17913, + "fuse_read_lane_wait_nanos": 2708329, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43311, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1877045 + } + }, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26182, + "attr_cache_misses": 30888, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33583, + "connection_wait_count": 33591, + "connection_wait_nanos": 10267426, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6131, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30303, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11766, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12041, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11766, + "fuse_op_getattr_nanos": 73590598, + "fuse_op_lookup_count": 12041, + "fuse_op_lookup_nanos": 130639620, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 101080070, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17921, + "fuse_read_lane_wait_nanos": 2711049, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43351, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20796, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14265929 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26182, + "attr_cache_misses": 30888, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33583, + "connection_wait_count": 33591, + "connection_wait_nanos": 10267426, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "getattr_count": 20796, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14265929 + }, + "fuse": { + "fuse_adapter_attr_hits": 6131, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30303, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11766, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12041, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 11766, + "fuse_op_getattr_nanos": 73590598, + "fuse_op_lookup_count": 12041, + "fuse_op_lookup_nanos": 130639620, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 101080070, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17921, + "fuse_read_lane_wait_nanos": 2711049, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43351, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564 + } + }, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26182, + "attr_cache_misses": 30888, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33583, + "connection_wait_count": 33591, + "connection_wait_nanos": 10267426, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7995, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5363, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32235, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13630, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12109, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13630, + "fuse_op_getattr_nanos": 74647885, + "fuse_op_lookup_count": 12109, + "fuse_op_lookup_nanos": 130727761, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 101084585, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17921, + "fuse_read_lane_wait_nanos": 2711049, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45295, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20796, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6883, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14265929 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26182, + "attr_cache_misses": 30888, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33583, + "connection_wait_count": 33591, + "connection_wait_nanos": 10267426, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "getattr_count": 20796, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6883, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14265929 + }, + "fuse": { + "fuse_adapter_attr_hits": 7995, + "fuse_adapter_attr_misses": 5635, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5363, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32235, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13630, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12109, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13630, + "fuse_op_getattr_nanos": 74647885, + "fuse_op_lookup_count": 12109, + "fuse_op_lookup_nanos": 130727761, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 101084585, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34400115, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 70175374, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17921, + "fuse_read_lane_wait_nanos": 2711049, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45295, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564 + } + }, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26185, + "attr_cache_misses": 30890, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33607, + "connection_wait_count": 33615, + "connection_wait_nanos": 10282838, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8039, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5610, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32551, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13675, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12360, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13675, + "fuse_op_getattr_nanos": 74728037, + "fuse_op_lookup_count": 12360, + "fuse_op_lookup_nanos": 131063668, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 101096061, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34728749, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 71183356, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17936, + "fuse_read_lane_wait_nanos": 2714753, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45647, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20798, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7130, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 14570016 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26185, + "attr_cache_misses": 30890, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33607, + "connection_wait_count": 33615, + "connection_wait_nanos": 10282838, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "getattr_count": 20798, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7130, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 14570016 + }, + "fuse": { + "fuse_adapter_attr_hits": 8039, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5610, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32551, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13675, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12360, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13675, + "fuse_op_getattr_nanos": 74728037, + "fuse_op_lookup_count": 12360, + "fuse_op_lookup_nanos": 131063668, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 101096061, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34728749, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 71183356, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17936, + "fuse_read_lane_wait_nanos": 2714753, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45647, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1878564 + } + }, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26185, + "attr_cache_misses": 30890, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33610, + "connection_wait_count": 33618, + "connection_wait_nanos": 10288353, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8039, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5610, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32551, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13675, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12360, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13675, + "fuse_op_getattr_nanos": 74728037, + "fuse_op_lookup_count": 12360, + "fuse_op_lookup_nanos": 131063668, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 101096061, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34728749, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 71183356, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17936, + "fuse_read_lane_wait_nanos": 2714753, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45647, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1879845, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20798, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7130, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 14693733 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 42, + "agentfs_batcher_commit_latency_ns_total": 801417963, + "agentfs_batcher_commit_txns": 4802, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26185, + "attr_cache_misses": 30890, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 8, + "connection_reuse": 33610, + "connection_wait_count": 33618, + "connection_wait_nanos": 10288353, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "getattr_count": 20798, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7130, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 14693733 + }, + "fuse": { + "fuse_adapter_attr_hits": 8039, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5610, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32551, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 655720, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13675, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12360, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 473455683, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 468, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 1059842, + "fuse_op_getattr_count": 13675, + "fuse_op_getattr_nanos": 74728037, + "fuse_op_lookup_count": 12360, + "fuse_op_lookup_nanos": 131063668, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 709, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 101096061, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34728749, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 71183356, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1137577682, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 25674858, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 7, + "fuse_read_lane_wait_count": 17936, + "fuse_read_lane_wait_nanos": 2714753, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45647, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1879845 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 64590, + "stderr_tail": "en_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":42,\"agentfs_batcher_commit_latency_ns_total\":801417963,\"agentfs_batcher_commit_txns\":4802,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26182,\"attr_cache_misses\":30888,\"chunk_read_chunks\":267,\"chunk_read_queries\":122,\"chunk_write_chunks\":1002,\"connection_create\":8,\"connection_reuse\":33583,\"connection_wait_count\":33591,\"connection_wait_nanos\":10267426,\"dentry_cache_hits\":26126,\"dentry_cache_misses\":14052,\"getattr_count\":20796,\"lookup_base_count\":23,\"lookup_count\":41054,\"lookup_delta_count\":7548,\"lookup_whiteout_count\":0,\"negative_cache_hits\":6883,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46085,\"negative_lookup_count\":14922,\"path_cache_hits\":26126,\"path_cache_misses\":14052,\"path_component_count\":37635,\"path_resolution_count\":8141,\"readdir_count\":1,\"readdir_plus_count\":1398,\"wal_checkpoint_count\":708,\"wal_checkpoint_nanos\":14265929},\"fuse\":{\"fuse_adapter_attr_hits\":7995,\"fuse_adapter_attr_misses\":5635,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6734,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5363,\"fuse_adapter_negative_misses\":6734,\"fuse_callback_count\":32235,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":655720,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13630,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12109,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":473455683,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":468,\"fuse_op_forget_count\":45,\"fuse_op_forget_nanos\":1059842,\"fuse_op_getattr_count\":13630,\"fuse_op_getattr_nanos\":74647885,\"fuse_op_lookup_count\":12109,\"fuse_op_lookup_nanos\":130727761,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":709,\"fuse_op_other_count\":3580,\"fuse_op_other_nanos\":101084585,\"fuse_op_read_count\":239,\"fuse_op_read_nanos\":34400115,\"fuse_op_readdirplus_count\":1407,\"fuse_op_readdirplus_nanos\":70175374,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1137577682,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":25674858,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":239,\"fuse_read_lane_max_concurrent\":7,\"fuse_read_lane_wait_count\":17921,\"fuse_read_lane_wait_nanos\":2711049,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1407,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45295,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":1878564}},\"source\":\"phase-checkpoint-6\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":42,\"agentfs_batcher_commit_latency_ns_total\":801417963,\"agentfs_batcher_commit_txns\":4802,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26185,\"attr_cache_misses\":30890,\"base_fast_inode_invalidations\":15122,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1002,\"connection_create\":8,\"connection_reuse\":33607,\"connection_wait_count\":33615,\"connection_wait_nanos\":10282838,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":8039,\"fuse_adapter_attr_misses\":5636,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5610,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32551,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":655720,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13675,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12360,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":473455683,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":468,\"fuse_op_forget_count\":45,\"fuse_op_forget_nanos\":1059842,\"fuse_op_getattr_count\":13675,\"fuse_op_getattr_nanos\":74728037,\"fuse_op_lookup_count\":12360,\"fuse_op_lookup_nanos\":131063668,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":709,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":101096061,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34728749,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":71183356,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1137577682,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":25674858,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":7,\"fuse_read_lane_wait_count\":17936,\"fuse_read_lane_wait_nanos\":2714753,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45647,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":1878564,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20798,\"lookup_base_count\":23,\"lookup_count\":41062,\"lookup_delta_count\":7552,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7130,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14924,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37677,\"path_resolution_count\":8151,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":717,\"wal_checkpoint_nanos\":14570016},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":15122,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":42,\"agentfs_batcher_commit_latency_ns_total\":801417963,\"agentfs_batcher_commit_txns\":4802,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26185,\"attr_cache_misses\":30890,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1002,\"connection_create\":8,\"connection_reuse\":33607,\"connection_wait_count\":33615,\"connection_wait_nanos\":10282838,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"getattr_count\":20798,\"lookup_base_count\":23,\"lookup_count\":41062,\"lookup_delta_count\":7552,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7130,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14924,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37677,\"path_resolution_count\":8151,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":717,\"wal_checkpoint_nanos\":14570016},\"fuse\":{\"fuse_adapter_attr_hits\":8039,\"fuse_adapter_attr_misses\":5636,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5610,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32551,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":655720,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13675,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12360,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":473455683,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":468,\"fuse_op_forget_count\":45,\"fuse_op_forget_nanos\":1059842,\"fuse_op_getattr_count\":13675,\"fuse_op_getattr_nanos\":74728037,\"fuse_op_lookup_count\":12360,\"fuse_op_lookup_nanos\":131063668,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":709,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":101096061,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34728749,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":71183356,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1137577682,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":25674858,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":7,\"fuse_read_lane_wait_count\":17936,\"fuse_read_lane_wait_nanos\":2714753,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45647,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":1878564}},\"source\":\"phase-checkpoint-7\"}\n\nSession: git-workload-14fbea9ebb104bf2b3f13d669d6de464\n\nTo resume this session:\n agentfs run --session git-workload-14fbea9ebb104bf2b3f13d669d6de464\n\nTo see what changed:\n agentfs diff git-workload-14fbea9ebb104bf2b3f13d669d6de464\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":42,\"agentfs_batcher_commit_latency_ns_total\":801417963,\"agentfs_batcher_commit_txns\":4802,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26185,\"attr_cache_misses\":30890,\"base_fast_inode_invalidations\":15122,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1002,\"connection_create\":8,\"connection_reuse\":33610,\"connection_wait_count\":33618,\"connection_wait_nanos\":10288353,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":8039,\"fuse_adapter_attr_misses\":5636,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5610,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32551,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":655720,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13675,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12360,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":473455683,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":468,\"fuse_op_forget_count\":45,\"fuse_op_forget_nanos\":1059842,\"fuse_op_getattr_count\":13675,\"fuse_op_getattr_nanos\":74728037,\"fuse_op_lookup_count\":12360,\"fuse_op_lookup_nanos\":131063668,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":709,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":101096061,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34728749,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":71183356,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1137577682,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":25674858,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":7,\"fuse_read_lane_wait_count\":17936,\"fuse_read_lane_wait_nanos\":2714753,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45647,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10270,\"fuse_write_lane_wait_nanos\":1879845,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20798,\"lookup_base_count\":23,\"lookup_count\":41062,\"lookup_delta_count\":7552,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7130,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14924,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37677,\"path_resolution_count\":8151,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":719,\"wal_checkpoint_nanos\":14693733},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":15122,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":42,\"agentfs_batcher_commit_latency_ns_total\":801417963,\"agentfs_batcher_commit_txns\":4802,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26185,\"attr_cache_misses\":30890,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1002,\"connection_create\":8,\"connection_reuse\":33610,\"connection_wait_count\":33618,\"connection_wait_nanos\":10288353,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"getattr_count\":20798,\"lookup_base_count\":23,\"lookup_count\":41062,\"lookup_delta_count\":7552,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7130,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14924,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37677,\"path_resolution_count\":8151,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":719,\"wal_checkpoint_nanos\":14693733},\"fuse\":{\"fuse_adapter_attr_hits\":8039,\"fuse_adapter_attr_misses\":5636,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5610,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32551,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":655720,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13675,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12360,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":473455683,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":468,\"fuse_op_forget_count\":45,\"fuse_op_forget_nanos\":1059842,\"fuse_op_getattr_count\":13675,\"fuse_op_getattr_nanos\":74728037,\"fuse_op_lookup_count\":12360,\"fuse_op_lookup_nanos\":131063668,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":709,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":101096061,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34728749,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":71183356,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1137577682,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":25674858,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":7,\"fuse_read_lane_wait_count\":17936,\"fuse_read_lane_wait_nanos\":2714753,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45647,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10270,\"fuse_write_lane_wait_nanos\":1879845}},\"source\":\"cli\"}\n", + "stdout_bytes": 18769, + "stdout_tail": "2026-07-04T01:13:54.510074Z INFO agentfs::fuser::session: resolved FUSE dispatch mode: parallel workers=7 queue_capacity=28\n2026-07-04T01:13:54.510556Z INFO agentfs::fuse: advertising FUSE_OVER_IO_URING\n2026-07-04T01:13:54.510626Z INFO agentfs::fuser::uring: starting fuse-over-io_uring queues nr_queues=14 depth=4\n2026-07-04T01:13:56.866873Z WARN agentfs::fuser::request: Request RequestId(52241): Failed to send reply: No such file or directory (os error 2)\n{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.06838756799697876, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work\", \"duration_seconds\": 0.0226461829879554, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work\", \"duration_seconds\": 0.01828943299187813, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work\", \"duration_seconds\": 0.027385373003198765, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.004524231000686996, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work\", \"duration_seconds\": 0.2221284370025387, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work\", \"duration_seconds\": 0.051958991010906175, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/mirror.git\", \"/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base\", \"duration_seconds\": 2.800840777999838, \"returncode\": 0, \"stderr_bytes\": 2340, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\nUpdating files: 37% (1753/4644)\\nUpdating files: 38% (1765/4644)\\nUpdating files: 39% (1812/4644)\\nUpdating files: 40% (1858/4644)\\nUpdating files: 41% (1905/4644)\\nUpdating files: 42% (1951/4644)\\nUpdating files: 43% (1997/4644)\\nUpdating files: 44% (2044/4644)\\nUpdating files: 45% (2090/4644)\\nUpdating files: 46% (2137/4644)\\nUpdating files: 47% (2183/4644)\\nUpdating files: 48% (2230/4644)\\nUpdating files: 49% (2276/4644)\\nUpdating files: 50% (2322/4644)\\nUpdating files: 51% (2369/4644)\\nUpdating files: 52% (2415/4644)\\nUpdating files: 53% (2462/4644)\\nUpdating files: 54% (2508/4644)\\nUpdating files: 55% (2555/4644)\\nUpdating files: 56% (2601/4644)\\nUpdating files: 57% (2648/4644)\\nUpdating files: 58% (2694/4644)\\nUpdating files: 59% (2740/4644)\\nUpdating files: 60% (2787/4644)\\nUpdating files: 61% (2833/4644)\\nUpdating files: 62% (2880/4644)\\nUpdating files: 63% (2926/4644)\\nUpdating files: 64% (2973/4644)\\nUpdating files: 65% (3019/4644)\\nUpdating files: 66% (3066/4644)\\nUpdating files: 67% (3112/4644)\\nUpdating files: 68% (3158/4644)\\nUpdating files: 69% (3205/4644)\\nUpdating files: 70% (3251/4644)\\nUpdating files: 71% (3298/4644)\\nUpdating files: 72% (3344/4644)\\nUpdating files: 73% (3391/4644)\\nUpdating files: 74% (3437/4644)\\nUpdating files: 75% (3483/4644)\\nUpdating files: 76% (3530/4644)\\nUpdating files: 77% (3576/4644)\\nUpdating files: 78% (3623/4644)\\nUpdating files: 79% (3669/4644)\\nUpdating files: 80% (3716/4644)\\nUpdating files: 80% (3761/4644)\\nUpdating files: 81% (3762/4644)\\nUpdating files: 82% (3809/4644)\\nUpdating files: 83% (3855/4644)\\nUpdating files: 84% (3901/4644)\\nUpdating files: 85% (3948/4644)\\nUpdating files: 86% (3994/4644)\\nUpdating files: 87% (4041/4644)\\nUpdating files: 88% (4087/4644)\\nUpdating files: 89% (4134/4644)\\nUpdating files: 90% (4180/4644)\\nUpdating files: 91% (4227/4644)\\nUpdating files: 92% (4273/4644)\\nUpdating files: 93% (4319/4644)\\nUpdating files: 94% (4366/4644)\\nUpdating files: 95% (4412/4644)\\nUpdating files: 96% (4459/4644)\\nUpdating files: 97% (4505/4644)\\nUpdating files: 98% (4552/4644)\\nUpdating files: 99% (4598/4644)\\nUpdating files: 100% (4644/4644)\\nUpdating files: 100% (4644/4644), done.\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work\", \"duration_seconds\": 0.03987898799823597, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work\", \"duration_seconds\": 0.13807101799466182, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.05482768401270732, \"clone\": 2.8008923250017688, \"diff\": 0.06838756799697876, \"edit\": 0.004524231000686996, \"fsck\": 0.22214840399101377, \"read_search\": 0.007138228989788331, \"status\": 0.17797551400144584}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work\", \"duration_seconds\": 0.00507977299275808, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 4.0367547670030035}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.06838756799697876, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work", + "duration_seconds": 0.0226461829879554, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work", + "duration_seconds": 0.01828943299187813, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work", + "duration_seconds": 0.027385373003198765, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.004524231000686996, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work", + "duration_seconds": 0.2221284370025387, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work", + "duration_seconds": 0.051958991010906175, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/mirror.git", + "/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base", + "duration_seconds": 2.800840777999838, + "returncode": 0, + "stderr_bytes": 2340, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\nUpdating files: 37% (1753/4644)\nUpdating files: 38% (1765/4644)\nUpdating files: 39% (1812/4644)\nUpdating files: 40% (1858/4644)\nUpdating files: 41% (1905/4644)\nUpdating files: 42% (1951/4644)\nUpdating files: 43% (1997/4644)\nUpdating files: 44% (2044/4644)\nUpdating files: 45% (2090/4644)\nUpdating files: 46% (2137/4644)\nUpdating files: 47% (2183/4644)\nUpdating files: 48% (2230/4644)\nUpdating files: 49% (2276/4644)\nUpdating files: 50% (2322/4644)\nUpdating files: 51% (2369/4644)\nUpdating files: 52% (2415/4644)\nUpdating files: 53% (2462/4644)\nUpdating files: 54% (2508/4644)\nUpdating files: 55% (2555/4644)\nUpdating files: 56% (2601/4644)\nUpdating files: 57% (2648/4644)\nUpdating files: 58% (2694/4644)\nUpdating files: 59% (2740/4644)\nUpdating files: 60% (2787/4644)\nUpdating files: 61% (2833/4644)\nUpdating files: 62% (2880/4644)\nUpdating files: 63% (2926/4644)\nUpdating files: 64% (2973/4644)\nUpdating files: 65% (3019/4644)\nUpdating files: 66% (3066/4644)\nUpdating files: 67% (3112/4644)\nUpdating files: 68% (3158/4644)\nUpdating files: 69% (3205/4644)\nUpdating files: 70% (3251/4644)\nUpdating files: 71% (3298/4644)\nUpdating files: 72% (3344/4644)\nUpdating files: 73% (3391/4644)\nUpdating files: 74% (3437/4644)\nUpdating files: 75% (3483/4644)\nUpdating files: 76% (3530/4644)\nUpdating files: 77% (3576/4644)\nUpdating files: 78% (3623/4644)\nUpdating files: 79% (3669/4644)\nUpdating files: 80% (3716/4644)\nUpdating files: 80% (3761/4644)\nUpdating files: 81% (3762/4644)\nUpdating files: 82% (3809/4644)\nUpdating files: 83% (3855/4644)\nUpdating files: 84% (3901/4644)\nUpdating files: 85% (3948/4644)\nUpdating files: 86% (3994/4644)\nUpdating files: 87% (4041/4644)\nUpdating files: 88% (4087/4644)\nUpdating files: 89% (4134/4644)\nUpdating files: 90% (4180/4644)\nUpdating files: 91% (4227/4644)\nUpdating files: 92% (4273/4644)\nUpdating files: 93% (4319/4644)\nUpdating files: 94% (4366/4644)\nUpdating files: 95% (4412/4644)\nUpdating files: 96% (4459/4644)\nUpdating files: 97% (4505/4644)\nUpdating files: 98% (4552/4644)\nUpdating files: 99% (4598/4644)\nUpdating files: 100% (4644/4644)\nUpdating files: 100% (4644/4644), done.\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work", + "duration_seconds": 0.03987898799823597, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work", + "duration_seconds": 0.13807101799466182, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.05482768401270732, + "clone": 2.8008923250017688, + "diff": 0.06838756799697876, + "edit": 0.004524231000686996, + "fsck": 0.22214840399101377, + "read_search": 0.007138228989788331, + "status": 0.17797551400144584 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/agentfs-base/work", + "duration_seconds": 0.00507977299275808, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 4.0367547670030035 + } + }, + "base_tree": { + "after": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "205674f335734baebd3d0abeeec63f4533006514fb18a46393d7c0f1a830c544", + "symlinks": 0 + }, + "before": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "205674f335734baebd3d0abeeec63f4533006514fb18a46393d7c0f1a830c544", + "symlinks": 0 + }, + "unchanged": true + }, + "benchmark": "phase7-git-workload", + "command": { + "agentfs_prefix": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-14fbea9ebb104bf2b3f13d669d6de464", + "--no-default-allows", + "--" + ], + "argv": [ + "/home/ain3sh/factory/vfs/scripts/validation/git-workload-benchmark.py", + "--output", + "/tmp/vfs-val/misc-a-bench-rebaseline/run3.json.iterations/iter-01.json", + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "workload_argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ] + }, + "correctness": { + "agentfs_backup_verify": true, + "agentfs_base_unchanged": true, + "agentfs_db_inspectable": true, + "agentfs_integrity_require_portable": true, + "agentfs_no_nonempty_sidecars": true, + "agentfs_portable": true, + "agentfs_returncode_zero": true, + "equivalence": { + "agentfs": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + }, + "checked": true, + "equivalent": true, + "native": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + } + }, + "native_returncode_zero": true, + "passed": true, + "performance_passed": false + }, + "database": { + "after": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-qcdrpuw6/home/.agentfs/run/git-workload-14fbea9ebb104bf2b3f13d669d6de464/delta.db" + } + ], + "path": "/tmp/agentfs-git-workload-qcdrpuw6/home/.agentfs/run/git-workload-14fbea9ebb104bf2b3f13d669d6de464/delta.db", + "total_bytes": 57147392 + }, + "backup": { + "artifacts": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-qcdrpuw6/git-workload-backup.db" + } + ], + "path": "/tmp/agentfs-git-workload-qcdrpuw6/git-workload-backup.db", + "total_bytes": 57147392 + }, + "inspect": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "path": "/tmp/agentfs-git-workload-qcdrpuw6/git-workload-backup.db", + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "backup", + "/tmp/agentfs-git-workload-qcdrpuw6/home/.agentfs/run/git-workload-14fbea9ebb104bf2b3f13d669d6de464/delta.db", + "/tmp/agentfs-git-workload-qcdrpuw6/git-workload-backup.db", + "--verify" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6", + "duration_seconds": 2.6113844479987165, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 0, + "fuse_readdirplus_mode": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_workers_configured": 0, + "fuse_writeback_cache_enabled": 0 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "fuse": { + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 7355, + "stderr_tail": "{\"counters\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_mode\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_workers_configured\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0,\"fuse_writeback_cache_enabled\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":0,\"fuse_readdirplus_mode\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_workers_configured\":0,\"fuse_writeback_cache_enabled\":0},\"core\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"fuse\":{\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0}},\"source\":\"cli\"}\n", + "stdout_bytes": 241, + "stdout_tail": "Source: /tmp/agentfs-git-workload-qcdrpuw6/home/.agentfs/run/git-workload-14fbea9ebb104bf2b3f13d669d6de464/delta.db\nBackup: /tmp/agentfs-git-workload-qcdrpuw6/git-workload-backup.db\nCheckpoint: complete\nCopy: complete\nVerification: complete\n", + "timed_out": false + } + }, + "inspect_after": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "integrity": { + "result": { + "checks": [ + { + "detail": "ok", + "name": "pragma.integrity_check", + "ok": true, + "violating_rows": null + }, + { + "detail": "present", + "name": "schema.table.fs_config", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_symlink", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.kv_store", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.tool_calls", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 0.5", + "name": "config.schema_version", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 65536", + "name": "config.chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 16384", + "name": "config.inline_threshold", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.kind_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_has_no_chunks", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunked_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_size_matches_blob", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.non_regular_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_reference_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunk_length_within_chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 1, expected 1", + "name": "namespace.root_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_is_directory", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_target_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_root_inode_has_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_names_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_directory_nlink_matches_dentries", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.directory_nlink_positive", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.rows_reference_symlink_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.inodes_have_rows", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable: no partial-origin rows", + "name": "overlay.portability_status", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable requirement satisfied", + "name": "overlay.require_portable", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_regular", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_sizes_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_paths_absolute", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_references_partial_origin", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_unique", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_index_in_range", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.whiteout_paths_absolute", + "ok": true, + "violating_rows": 0 + } + ], + "database": "/tmp/agentfs-git-workload-qcdrpuw6/home/.agentfs/run/git-workload-14fbea9ebb104bf2b3f13d669d6de464/delta.db", + "ok": true, + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true + }, + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "integrity", + "/tmp/agentfs-git-workload-qcdrpuw6/home/.agentfs/run/git-workload-14fbea9ebb104bf2b3f13d669d6de464/delta.db", + "--json", + "--require-portable" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6", + "duration_seconds": 2.6462612470058957, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 0, + "fuse_readdirplus_mode": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_workers_configured": 0, + "fuse_writeback_cache_enabled": 0 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "fuse": { + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 7355, + "stderr_tail": "{\"counters\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_mode\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_workers_configured\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0,\"fuse_writeback_cache_enabled\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":0,\"fuse_readdirplus_mode\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_workers_configured\":0,\"fuse_writeback_cache_enabled\":0},\"core\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"fuse\":{\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0}},\"source\":\"cli\"}\n", + "stdout_bytes": 6395, + "stdout_tail": "{\n \"database\": \"/tmp/agentfs-git-workload-qcdrpuw6/home/.agentfs/run/git-workload-14fbea9ebb104bf2b3f13d669d6de464/delta.db\",\n \"ok\": true,\n \"portable\": true,\n \"origin_backed\": false,\n \"partial_origin_rows\": 0,\n \"checks\": [\n {\n \"name\": \"pragma.integrity_check\",\n \"ok\": true,\n \"detail\": \"ok\",\n \"violating_rows\": null\n },\n {\n \"name\": \"schema.table.fs_config\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_inode\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_dentry\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_data\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_symlink\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.kv_store\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.tool_calls\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.schema_version\",\n \"ok\": true,\n \"detail\": \"found 0.5\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.chunk_size\",\n \"ok\": true,\n \"detail\": \"found 65536\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.inline_threshold\",\n \"ok\": true,\n \"detail\": \"found 16384\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.kind_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_has_no_chunks\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunked_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_size_matches_blob\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.non_regular_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_reference_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunk_length_within_chunk_size\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.root_inode\",\n \"ok\": true,\n \"detail\": \"found 1, expected 1\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_is_directory\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_target_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_root_inode_has_dentry\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_names_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_directory_nlink_matches_dentries\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.directory_nlink_positive\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.rows_reference_symlink_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.inodes_have_rows\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.portability_status\",\n \"ok\": true,\n \"detail\": \"portable: no partial-origin rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.require_portable\",\n \"ok\": true,\n \"detail\": \"portable requirement satisfied\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_regular\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_sizes_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_references_partial_origin\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_unique\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_index_in_range\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.whiteout_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n }\n ]\n}\n", + "timed_out": false + } + }, + "nonempty_sidecars": false + }, + "environment": { + "AGENTFS_BIN": "/home/ain3sh/factory/vfs/target/release/agentfs", + "AGENTFS_PROFILE": "1" + }, + "git_commit": "2b811f03493e861e5a7786592d25ac73b39e1aba", + "kept_temp": false, + "native": { + "run": { + "argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/native", + "duration_seconds": 0.8829451810015598, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 15992, + "stdout_tail": "{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.013687198006664403, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/native/work\", \"duration_seconds\": 0.004608689007000066, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/native/work\", \"duration_seconds\": 0.004381564998766407, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/native/work\", \"duration_seconds\": 0.004656777004129253, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.000326687004417181, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/native/work\", \"duration_seconds\": 0.23136319500918034, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/native/work\", \"duration_seconds\": 0.23782009299611673, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-qcdrpuw6/native/mirror.git\", \"/tmp/agentfs-git-workload-qcdrpuw6/native/work\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/native\", \"duration_seconds\": 0.32865579999634065, \"returncode\": 0, \"stderr_bytes\": 149, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-qcdrpuw6/native/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/native/work\", \"duration_seconds\": 0.00893766999070067, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/native/work\", \"duration_seconds\": 0.009510732008493505, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.24071779299993068, \"clone\": 0.3286946500011254, \"diff\": 0.013687198006664403, \"edit\": 0.000326687004417181, \"fsck\": 0.23137624299852178, \"read_search\": 0.0063567659963155165, \"status\": 0.018477466990589164}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-qcdrpuw6/native/work\", \"duration_seconds\": 0.003808831999776885, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 0.8398232860054122}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.013687198006664403, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/native/work", + "duration_seconds": 0.004608689007000066, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/native/work", + "duration_seconds": 0.004381564998766407, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/native/work", + "duration_seconds": 0.004656777004129253, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.000326687004417181, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/native/work", + "duration_seconds": 0.23136319500918034, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/native/work", + "duration_seconds": 0.23782009299611673, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-qcdrpuw6/native/mirror.git", + "/tmp/agentfs-git-workload-qcdrpuw6/native/work" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/native", + "duration_seconds": 0.32865579999634065, + "returncode": 0, + "stderr_bytes": 149, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-qcdrpuw6/native/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/native/work", + "duration_seconds": 0.00893766999070067, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/native/work", + "duration_seconds": 0.009510732008493505, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.24071779299993068, + "clone": 0.3286946500011254, + "diff": 0.013687198006664403, + "edit": 0.000326687004417181, + "fsck": 0.23137624299852178, + "read_search": 0.0063567659963155165, + "status": 0.018477466990589164 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-qcdrpuw6/native/work", + "duration_seconds": 0.003808831999776885, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 0.8398232860054122 + } + }, + "parameters": { + "edit_files": 8, + "fixture_dirs": 8, + "fixture_file_size_bytes": 1024, + "fixture_files": 96, + "read_bytes": 4096, + "read_files": 64, + "search_token": "AGENTFS_TOKEN", + "skip_fsck": false, + "timeout_seconds": 180.0 + }, + "schema_version": 1, + "source": { + "kind": "source", + "mirror_head": "7d47056ea42636271ac020b86347fbbef49490aa", + "path": "/home/ain3sh/factory/vfs/.agents/benchmarks/fixtures/codex" + }, + "summary": { + "agentfs_base_unchanged": true, + "agentfs_seconds": 4.0367547670030035, + "all_equivalent": true, + "correctness_passed": true, + "native_seconds": 0.8398232860054122, + "passed": true, + "performance_passed": false, + "phase_ratios": { + "checkout": { + "agentfs_seconds": 0.05482768401270732, + "native_seconds": 0.24071779299993068, + "ratio": 0.22776747547167447 + }, + "clone": { + "agentfs_seconds": 2.8008923250017688, + "native_seconds": 0.3286946500011254, + "ratio": 8.521259244688585 + }, + "diff": { + "agentfs_seconds": 0.06838756799697876, + "native_seconds": 0.013687198006664403, + "ratio": 4.996462238924309 + }, + "edit": { + "agentfs_seconds": 0.004524231000686996, + "native_seconds": 0.000326687004417181, + "ratio": 13.848824530863581 + }, + "fsck": { + "agentfs_seconds": 0.22214840399101377, + "native_seconds": 0.23137624299852178, + "ratio": 0.9601176037439291 + }, + "read_search": { + "agentfs_seconds": 0.007138228989788331, + "native_seconds": 0.0063567659963155165, + "ratio": 1.1229340507304757 + }, + "status": { + "agentfs_seconds": 0.17797551400144584, + "native_seconds": 0.018477466990589164, + "ratio": 9.632029871415346 + } + }, + "ratio": 4.806671634700289, + "threshold_failures": [ + { + "agentfs_seconds": 2.8008923250017688, + "native_seconds": 0.3286946500011254, + "phase": "clone", + "ratio": 8.521259244688585 + }, + { + "agentfs_seconds": 0.06838756799697876, + "native_seconds": 0.013687198006664403, + "phase": "diff", + "ratio": 4.996462238924309 + }, + { + "agentfs_seconds": 0.004524231000686996, + "native_seconds": 0.000326687004417181, + "phase": "edit", + "ratio": 13.848824530863581 + }, + { + "agentfs_seconds": 0.17797551400144584, + "native_seconds": 0.018477466990589164, + "phase": "status", + "ratio": 9.632029871415346 + } + ] + }, + "temp_dir": "/tmp/agentfs-git-workload-qcdrpuw6" +} diff --git a/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-02.json b/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-02.json new file mode 100644 index 00000000..76509b35 --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-02.json @@ -0,0 +1,8674 @@ +{ + "agentfs": { + "bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "db_path": "/tmp/agentfs-git-workload-bkhbczd5/home/.agentfs/run/git-workload-e09c5e33cc4249779c53f1e85f08fbe4/delta.db", + "per_phase_counters": { + "checkpoint_count": 7, + "label_count": 7, + "labels_aligned": true, + "phases": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 792864828, + "agentfs_batcher_commit_txns": 4780, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29045, + "base_fast_inode_invalidations": 15025, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create": 2, + "connection_reuse": 31198, + "connection_wait_count": 31200, + "connection_wait_nanos": 9492867, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 91, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5281, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21779, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 34, + "fuse_dispatch_wait_count": 34, + "fuse_dispatch_wait_nanos": 571446, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52448340, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5025, + "fuse_ino_file_resolutions": 18, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11698, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 460793278, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 31, + "fuse_op_forget_nanos": 913722, + "fuse_op_getattr_count": 5025, + "fuse_op_getattr_nanos": 50503736, + "fuse_op_lookup_count": 11698, + "fuse_op_lookup_nanos": 126133971, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 96227847, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 30116469, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2099847, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1115862338, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 25063983, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16147, + "fuse_read_lane_wait_nanos": 2404255, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32002, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1794011, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19330, + "lookup_base_count": 23, + "lookup_count": 40286, + "lookup_delta_count": 7190, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6774, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14254, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32899, + "path_resolution_count": 7117, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1649454 + }, + "phase": "clone", + "seq": 1 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 2, + "agentfs_batcher_commit_latency_ns_total": 5784853, + "agentfs_batcher_commit_txns": 11, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 5, + "agentfs_batcher_drains_timer": 3, + "agentfs_batcher_enqueues": 10, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 15, + "attr_cache_hits": 242, + "attr_cache_misses": 1775, + "base_fast_inode_invalidations": 51, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 15, + "connection_create": 7, + "connection_reuse": 625, + "connection_wait_count": 632, + "connection_wait_nanos": 293118, + "dentry_cache_hits": 73, + "dentry_cache_misses": 56, + "fuse_adapter_attr_hits": 4561, + "fuse_adapter_attr_misses": 696, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 35, + "fuse_adapter_inval_entry_notifications": 10, + "fuse_adapter_inval_inode_notifications": 28, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 11, + "fuse_adapter_negative_misses": 35, + "fuse_callback_count": 5326, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 8, + "fuse_dispatch_wait_count": 8, + "fuse_dispatch_wait_nanos": 48135, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 785936, + "fuse_flush_count": 10, + "fuse_flush_ranges": 10, + "fuse_getattr_count": 5257, + "fuse_ino_file_resolutions": 3, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 48, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 9, + "fuse_op_create_nanos": 1226165, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 8, + "fuse_op_forget_nanos": 81861, + "fuse_op_getattr_count": 5257, + "fuse_op_getattr_nanos": 29609297, + "fuse_op_lookup_count": 48, + "fuse_op_lookup_nanos": 719726, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 14, + "fuse_op_other_nanos": 3128142, + "fuse_op_read_count": 8, + "fuse_op_read_nanos": 2320137, + "fuse_op_readdirplus_count": 4, + "fuse_op_readdirplus_nanos": 1192686, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 14, + "fuse_op_setattr_nanos": 4756141, + "fuse_op_write_count": 9, + "fuse_op_write_nanos": 321881, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 8, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 760, + "fuse_read_lane_wait_nanos": 122421, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 4, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5363, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 591393, + "fuse_write_count": 9, + "fuse_write_lane_wait_count": 42, + "fuse_write_lane_wait_nanos": 50042, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 1420, + "lookup_base_count": 0, + "lookup_count": 164, + "lookup_delta_count": 60, + "lookup_whiteout_count": 0, + "negative_cache_hits": 32, + "negative_cache_invalidations": 26, + "negative_cache_misses": 148, + "negative_lookup_count": 86, + "path_cache_hits": 73, + "path_cache_misses": 56, + "path_component_count": 124, + "path_resolution_count": 45, + "readdir_count": 0, + "readdir_plus_count": 4, + "wal_checkpoint_count": 2, + "wal_checkpoint_nanos": 844380 + }, + "phase": "checkout", + "seq": 2 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 4, + "agentfs_batcher_commit_latency_ns_total": 3833308, + "agentfs_batcher_commit_txns": 4, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 2, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 6, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 4, + "attr_cache_hits": 18, + "attr_cache_misses": 35, + "base_fast_inode_invalidations": 24, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 20, + "chunk_read_queries": 12, + "chunk_write_chunks": 20, + "connection_create": 0, + "connection_reuse": 1705, + "connection_wait_count": 1705, + "connection_wait_nanos": 378304, + "dentry_cache_hits": 19, + "dentry_cache_misses": 570, + "fuse_adapter_attr_hits": 875, + "fuse_adapter_attr_misses": 6, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 289, + "fuse_adapter_inval_entry_notifications": 4, + "fuse_adapter_inval_inode_notifications": 8, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 289, + "fuse_callback_count": 2575, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 4, + "fuse_dispatch_wait_count": 4, + "fuse_dispatch_wait_nanos": 59380, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 1181458, + "fuse_flush_count": 6, + "fuse_flush_ranges": 6, + "fuse_getattr_count": 881, + "fuse_ino_file_resolutions": 2, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 291, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 2, + "fuse_op_create_nanos": 619475, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 4, + "fuse_op_forget_nanos": 31731, + "fuse_op_getattr_count": 881, + "fuse_op_getattr_nanos": 1033831, + "fuse_op_lookup_count": 291, + "fuse_op_lookup_nanos": 4681706, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 2762, + "fuse_op_other_nanos": 1826905, + "fuse_op_read_count": 12, + "fuse_op_read_nanos": 2209197, + "fuse_op_readdirplus_count": 1381, + "fuse_op_readdirplus_nanos": 59557332, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 6, + "fuse_op_setattr_nanos": 6018352, + "fuse_op_write_count": 10, + "fuse_op_write_nanos": 1908780, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 12, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 995, + "fuse_read_lane_wait_nanos": 119240, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1381, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5345, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 1181458, + "fuse_write_count": 10, + "fuse_write_lane_wait_count": 14, + "fuse_write_lane_wait_nanos": 10741, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 24, + "lookup_base_count": 0, + "lookup_count": 598, + "lookup_delta_count": 295, + "lookup_whiteout_count": 0, + "negative_cache_hits": 4, + "negative_cache_invalidations": 4, + "negative_cache_misses": 873, + "negative_lookup_count": 578, + "path_cache_hits": 19, + "path_cache_misses": 570, + "path_component_count": 4606, + "path_resolution_count": 977, + "readdir_count": 0, + "readdir_plus_count": 1376, + "wal_checkpoint_count": 688, + "wal_checkpoint_nanos": 9148410 + }, + "phase": "status", + "seq": 3 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 1, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 8, + "connection_wait_count": 8, + "connection_wait_nanos": 5583, + "dentry_cache_hits": 1, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 69, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 1, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 1, + "fuse_callback_count": 76, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 69, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 1, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 69, + "fuse_op_getattr_nanos": 74101, + "fuse_op_lookup_count": 1, + "fuse_op_lookup_nanos": 25064, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 6, + "fuse_op_read_nanos": 806933, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 6, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 2, + "fuse_read_lane_wait_nanos": 570, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 76, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 2, + "lookup_delta_count": 1, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 2, + "negative_lookup_count": 0, + "path_cache_hits": 1, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "read_search", + "seq": 4 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 1367975, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 8, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 8, + "attr_cache_hits": 0, + "attr_cache_misses": 24, + "base_fast_inode_invalidations": 16, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 32, + "connection_wait_count": 32, + "connection_wait_nanos": 16669, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 8, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 16, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 6398, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 8, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 8, + "fuse_op_getattr_nanos": 9464, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 8, + "fuse_op_other_nanos": 994214, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 8, + "fuse_op_setattr_nanos": 2489436, + "fuse_op_write_count": 8, + "fuse_op_write_nanos": 468679, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 8, + "fuse_read_lane_wait_nanos": 2666, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 32, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 6398, + "fuse_write_count": 8, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 1688, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 16, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 8, + "wal_checkpoint_nanos": 847497 + }, + "phase": "edit", + "seq": 5 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 353, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 14, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 367, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 353, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 14, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 353, + "fuse_op_getattr_nanos": 320738, + "fuse_op_lookup_count": 14, + "fuse_op_lookup_nanos": 14429, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 12, + "fuse_op_other_nanos": 10349, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 379, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 14, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "diff", + "seq": 6 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 3, + "attr_cache_misses": 2, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 1, + "chunk_read_queries": 1, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 24, + "connection_wait_count": 24, + "connection_wait_nanos": 15069, + "dentry_cache_hits": 4, + "dentry_cache_misses": 2, + "fuse_adapter_attr_hits": 44, + "fuse_adapter_attr_misses": 1, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 4, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 234, + "fuse_adapter_negative_misses": 4, + "fuse_callback_count": 303, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 45, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 238, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 45, + "fuse_op_getattr_nanos": 79538, + "fuse_op_lookup_count": 238, + "fuse_op_lookup_nanos": 285815, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 36, + "fuse_op_other_nanos": 10146, + "fuse_op_read_count": 2, + "fuse_op_read_nanos": 365484, + "fuse_op_readdirplus_count": 18, + "fuse_op_readdirplus_nanos": 938390, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 2, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 15, + "fuse_read_lane_wait_nanos": 3512, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 18, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 339, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 2, + "lookup_base_count": 0, + "lookup_count": 8, + "lookup_delta_count": 4, + "lookup_whiteout_count": 0, + "negative_cache_hits": 234, + "negative_cache_invalidations": 0, + "negative_cache_misses": 9, + "negative_lookup_count": 2, + "path_cache_hits": 4, + "path_cache_misses": 2, + "path_component_count": 42, + "path_resolution_count": 10, + "readdir_count": 0, + "readdir_plus_count": 18, + "wal_checkpoint_count": 9, + "wal_checkpoint_nanos": 277412 + }, + "phase": "fsck", + "seq": 7 + } + ] + }, + "profile_counters": { + "last_by_source": { + "cli": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26185, + "attr_cache_misses": 30881, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33595, + "connection_wait_count": 33604, + "connection_wait_nanos": 10208367, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6001, + "fuse_adapter_attr_misses": 5637, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5540, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 30442, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11638, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12290, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11638, + "fuse_op_getattr_nanos": 81630705, + "fuse_op_lookup_count": 12290, + "fuse_op_lookup_nanos": 131860711, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 102197603, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 35818220, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 63788255, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2652664, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43536, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1858106, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20792, + "lookup_base_count": 23, + "lookup_count": 41058, + "lookup_delta_count": 7550, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7058, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14920, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 12934046 + }, + "phase-checkpoint-1": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 792864828, + "agentfs_batcher_commit_txns": 4780, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29045, + "base_fast_inode_invalidations": 15025, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create": 2, + "connection_reuse": 31198, + "connection_wait_count": 31200, + "connection_wait_nanos": 9492867, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 91, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5281, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21779, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 34, + "fuse_dispatch_wait_count": 34, + "fuse_dispatch_wait_nanos": 571446, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52448340, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5025, + "fuse_ino_file_resolutions": 18, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11698, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 460793278, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 31, + "fuse_op_forget_nanos": 913722, + "fuse_op_getattr_count": 5025, + "fuse_op_getattr_nanos": 50503736, + "fuse_op_lookup_count": 11698, + "fuse_op_lookup_nanos": 126133971, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 96227847, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 30116469, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2099847, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1115862338, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 25063983, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16147, + "fuse_read_lane_wait_nanos": 2404255, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32002, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1794011, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19330, + "lookup_base_count": 23, + "lookup_count": 40286, + "lookup_delta_count": 7190, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6774, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14254, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32899, + "path_resolution_count": 7117, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1649454 + }, + "phase-checkpoint-2": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 798649681, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26163, + "attr_cache_misses": 30820, + "base_fast_inode_invalidations": 15076, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 9, + "connection_reuse": 31823, + "connection_wait_count": 31832, + "connection_wait_nanos": 9785985, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4652, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27105, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 42, + "fuse_dispatch_wait_count": 42, + "fuse_dispatch_wait_nanos": 619581, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10282, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11746, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 462019443, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 39, + "fuse_op_forget_nanos": 995583, + "fuse_op_getattr_count": 10282, + "fuse_op_getattr_nanos": 80113033, + "fuse_op_lookup_count": 11746, + "fuse_op_lookup_nanos": 126853697, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 99355989, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 32436606, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3292533, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1120618479, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 25385864, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16907, + "fuse_read_lane_wait_nanos": 2526676, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37365, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1844053, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20750, + "lookup_base_count": 23, + "lookup_count": 40450, + "lookup_delta_count": 7250, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14340, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33023, + "path_resolution_count": 7162, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2493834 + }, + "phase-checkpoint-3": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 802482989, + "agentfs_batcher_commit_txns": 4795, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26181, + "attr_cache_misses": 30855, + "base_fast_inode_invalidations": 15100, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33528, + "connection_wait_count": 33537, + "connection_wait_nanos": 10164289, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5527, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 29680, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11163, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12037, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11163, + "fuse_op_getattr_nanos": 81146864, + "fuse_op_lookup_count": 12037, + "fuse_op_lookup_nanos": 131535403, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 101182894, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 34645803, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1126636831, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 27294644, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2645916, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 42710, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1854794, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20774, + "lookup_base_count": 23, + "lookup_count": 41048, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14918, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11642244 + }, + "phase-checkpoint-4": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 802482989, + "agentfs_batcher_commit_txns": 4795, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26182, + "attr_cache_misses": 30855, + "base_fast_inode_invalidations": 15100, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33536, + "connection_wait_count": 33545, + "connection_wait_nanos": 10169872, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5596, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 29756, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11232, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11232, + "fuse_op_getattr_nanos": 81220965, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 131560467, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 101182894, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1126636831, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 27294644, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17904, + "fuse_read_lane_wait_nanos": 2646486, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 42786, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1854794, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20774, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11642244 + }, + "phase-checkpoint-5": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26182, + "attr_cache_misses": 30879, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33568, + "connection_wait_count": 33577, + "connection_wait_nanos": 10186541, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5604, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 29772, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11240, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11240, + "fuse_op_getattr_nanos": 81230429, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 131560467, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 102177108, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17912, + "fuse_read_lane_wait_nanos": 2649152, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 42818, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20790, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 12489741 + }, + "phase-checkpoint-6": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26182, + "attr_cache_misses": 30879, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33568, + "connection_wait_count": 33577, + "connection_wait_nanos": 10186541, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5957, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5306, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30139, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11593, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12052, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11593, + "fuse_op_getattr_nanos": 81551167, + "fuse_op_lookup_count": 12052, + "fuse_op_lookup_nanos": 131574896, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 102187457, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17912, + "fuse_read_lane_wait_nanos": 2649152, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43197, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20790, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6824, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 12489741 + }, + "phase-checkpoint-7": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26185, + "attr_cache_misses": 30881, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33592, + "connection_wait_count": 33601, + "connection_wait_nanos": 10201610, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6001, + "fuse_adapter_attr_misses": 5637, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5540, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 30442, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11638, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12290, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11638, + "fuse_op_getattr_nanos": 81630705, + "fuse_op_lookup_count": 12290, + "fuse_op_lookup_nanos": 131860711, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 102197603, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 35818220, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 63788255, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2652664, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43536, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20792, + "lookup_base_count": 23, + "lookup_count": 41058, + "lookup_delta_count": 7550, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7058, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14920, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 12767153 + } + }, + "max_counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26185, + "attr_cache_misses": 30881, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33595, + "connection_wait_count": 33604, + "connection_wait_nanos": 10208367, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6001, + "fuse_adapter_attr_misses": 5637, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5540, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 30442, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11638, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12290, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11638, + "fuse_op_getattr_nanos": 81630705, + "fuse_op_lookup_count": 12290, + "fuse_op_lookup_nanos": 131860711, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 102197603, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 35818220, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 63788255, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2652664, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43536, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1858106, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20792, + "lookup_base_count": 23, + "lookup_count": 41058, + "lookup_delta_count": 7550, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7058, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14920, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 12934046 + }, + "summary_count": 8 + }, + "profile_enabled": true, + "profile_summary_count": 8, + "session": "git-workload-e09c5e33cc4249779c53f1e85f08fbe4" + }, + "agentfs_overlay": { + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 792864828, + "agentfs_batcher_commit_txns": 4780, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29045, + "base_fast_inode_invalidations": 15025, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create": 2, + "connection_reuse": 31198, + "connection_wait_count": 31200, + "connection_wait_nanos": 9492867, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 91, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5281, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21779, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 34, + "fuse_dispatch_wait_count": 34, + "fuse_dispatch_wait_nanos": 571446, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52448340, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5025, + "fuse_ino_file_resolutions": 18, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11698, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 460793278, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 31, + "fuse_op_forget_nanos": 913722, + "fuse_op_getattr_count": 5025, + "fuse_op_getattr_nanos": 50503736, + "fuse_op_lookup_count": 11698, + "fuse_op_lookup_nanos": 126133971, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 96227847, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 30116469, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2099847, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1115862338, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 25063983, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16147, + "fuse_read_lane_wait_nanos": 2404255, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32002, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1794011, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19330, + "lookup_base_count": 23, + "lookup_count": 40286, + "lookup_delta_count": 7190, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6774, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14254, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32899, + "path_resolution_count": 7117, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1649454 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15025, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 792864828, + "agentfs_batcher_commit_txns": 4780, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29045, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create": 2, + "connection_reuse": 31198, + "connection_wait_count": 31200, + "connection_wait_nanos": 9492867, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "getattr_count": 19330, + "lookup_base_count": 23, + "lookup_count": 40286, + "lookup_delta_count": 7190, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6774, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14254, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32899, + "path_resolution_count": 7117, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1649454 + }, + "fuse": { + "fuse_adapter_attr_hits": 91, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5281, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21779, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 34, + "fuse_dispatch_wait_count": 34, + "fuse_dispatch_wait_nanos": 571446, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52448340, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5025, + "fuse_ino_file_resolutions": 18, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11698, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 460793278, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 31, + "fuse_op_forget_nanos": 913722, + "fuse_op_getattr_count": 5025, + "fuse_op_getattr_nanos": 50503736, + "fuse_op_lookup_count": 11698, + "fuse_op_lookup_nanos": 126133971, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 96227847, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 30116469, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2099847, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1115862338, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 25063983, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16147, + "fuse_read_lane_wait_nanos": 2404255, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 32002, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1794011 + } + }, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 798649681, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26163, + "attr_cache_misses": 30820, + "base_fast_inode_invalidations": 15076, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 9, + "connection_reuse": 31823, + "connection_wait_count": 31832, + "connection_wait_nanos": 9785985, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4652, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27105, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 42, + "fuse_dispatch_wait_count": 42, + "fuse_dispatch_wait_nanos": 619581, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10282, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11746, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 462019443, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 39, + "fuse_op_forget_nanos": 995583, + "fuse_op_getattr_count": 10282, + "fuse_op_getattr_nanos": 80113033, + "fuse_op_lookup_count": 11746, + "fuse_op_lookup_nanos": 126853697, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 99355989, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 32436606, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3292533, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1120618479, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 25385864, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16907, + "fuse_read_lane_wait_nanos": 2526676, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37365, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1844053, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20750, + "lookup_base_count": 23, + "lookup_count": 40450, + "lookup_delta_count": 7250, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14340, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33023, + "path_resolution_count": 7162, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2493834 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15076, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 798649681, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26163, + "attr_cache_misses": 30820, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 9, + "connection_reuse": 31823, + "connection_wait_count": 31832, + "connection_wait_nanos": 9785985, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "getattr_count": 20750, + "lookup_base_count": 23, + "lookup_count": 40450, + "lookup_delta_count": 7250, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14340, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33023, + "path_resolution_count": 7162, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2493834 + }, + "fuse": { + "fuse_adapter_attr_hits": 4652, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27105, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 42, + "fuse_dispatch_wait_count": 42, + "fuse_dispatch_wait_nanos": 619581, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10282, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11746, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 462019443, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 39, + "fuse_op_forget_nanos": 995583, + "fuse_op_getattr_count": 10282, + "fuse_op_getattr_nanos": 80113033, + "fuse_op_lookup_count": 11746, + "fuse_op_lookup_nanos": 126853697, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 99355989, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 32436606, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3292533, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1120618479, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 25385864, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16907, + "fuse_read_lane_wait_nanos": 2526676, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 37365, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1844053 + } + }, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 802482989, + "agentfs_batcher_commit_txns": 4795, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26181, + "attr_cache_misses": 30855, + "base_fast_inode_invalidations": 15100, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33528, + "connection_wait_count": 33537, + "connection_wait_nanos": 10164289, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5527, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 29680, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11163, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12037, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11163, + "fuse_op_getattr_nanos": 81146864, + "fuse_op_lookup_count": 12037, + "fuse_op_lookup_nanos": 131535403, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 101182894, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 34645803, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1126636831, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 27294644, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2645916, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 42710, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1854794, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20774, + "lookup_base_count": 23, + "lookup_count": 41048, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14918, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11642244 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15100, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 802482989, + "agentfs_batcher_commit_txns": 4795, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26181, + "attr_cache_misses": 30855, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33528, + "connection_wait_count": 33537, + "connection_wait_nanos": 10164289, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "getattr_count": 20774, + "lookup_base_count": 23, + "lookup_count": 41048, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14918, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11642244 + }, + "fuse": { + "fuse_adapter_attr_hits": 5527, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 29680, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11163, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12037, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11163, + "fuse_op_getattr_nanos": 81146864, + "fuse_op_lookup_count": 12037, + "fuse_op_lookup_nanos": 131535403, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 101182894, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 34645803, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1126636831, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 27294644, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2645916, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 42710, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1854794 + } + }, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 802482989, + "agentfs_batcher_commit_txns": 4795, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26182, + "attr_cache_misses": 30855, + "base_fast_inode_invalidations": 15100, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33536, + "connection_wait_count": 33545, + "connection_wait_nanos": 10169872, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5596, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 29756, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11232, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11232, + "fuse_op_getattr_nanos": 81220965, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 131560467, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 101182894, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1126636831, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 27294644, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17904, + "fuse_read_lane_wait_nanos": 2646486, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 42786, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1854794, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20774, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11642244 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15100, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 802482989, + "agentfs_batcher_commit_txns": 4795, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26182, + "attr_cache_misses": 30855, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33536, + "connection_wait_count": 33545, + "connection_wait_nanos": 10169872, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20774, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11642244 + }, + "fuse": { + "fuse_adapter_attr_hits": 5596, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 29756, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11232, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11232, + "fuse_op_getattr_nanos": 81220965, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 131560467, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 101182894, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1126636831, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 27294644, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17904, + "fuse_read_lane_wait_nanos": 2646486, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 42786, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1854794 + } + }, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26182, + "attr_cache_misses": 30879, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33568, + "connection_wait_count": 33577, + "connection_wait_nanos": 10186541, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5604, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 29772, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11240, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11240, + "fuse_op_getattr_nanos": 81230429, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 131560467, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 102177108, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17912, + "fuse_read_lane_wait_nanos": 2649152, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 42818, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20790, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 12489741 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26182, + "attr_cache_misses": 30879, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33568, + "connection_wait_count": 33577, + "connection_wait_nanos": 10186541, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20790, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 12489741 + }, + "fuse": { + "fuse_adapter_attr_hits": 5604, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 29772, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11240, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11240, + "fuse_op_getattr_nanos": 81230429, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 131560467, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 102177108, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17912, + "fuse_read_lane_wait_nanos": 2649152, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 42818, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482 + } + }, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26182, + "attr_cache_misses": 30879, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33568, + "connection_wait_count": 33577, + "connection_wait_nanos": 10186541, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5957, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5306, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30139, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11593, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12052, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11593, + "fuse_op_getattr_nanos": 81551167, + "fuse_op_lookup_count": 12052, + "fuse_op_lookup_nanos": 131574896, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 102187457, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17912, + "fuse_read_lane_wait_nanos": 2649152, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43197, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20790, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6824, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 12489741 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26182, + "attr_cache_misses": 30879, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33568, + "connection_wait_count": 33577, + "connection_wait_nanos": 10186541, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20790, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6824, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 12489741 + }, + "fuse": { + "fuse_adapter_attr_hits": 5957, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5306, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30139, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11593, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12052, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11593, + "fuse_op_getattr_nanos": 81551167, + "fuse_op_lookup_count": 12052, + "fuse_op_lookup_nanos": 131574896, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 102187457, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17912, + "fuse_read_lane_wait_nanos": 2649152, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43197, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482 + } + }, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26185, + "attr_cache_misses": 30881, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33592, + "connection_wait_count": 33601, + "connection_wait_nanos": 10201610, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6001, + "fuse_adapter_attr_misses": 5637, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5540, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 30442, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11638, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12290, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11638, + "fuse_op_getattr_nanos": 81630705, + "fuse_op_lookup_count": 12290, + "fuse_op_lookup_nanos": 131860711, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 102197603, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 35818220, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 63788255, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2652664, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43536, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20792, + "lookup_base_count": 23, + "lookup_count": 41058, + "lookup_delta_count": 7550, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7058, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14920, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 12767153 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26185, + "attr_cache_misses": 30881, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33592, + "connection_wait_count": 33601, + "connection_wait_nanos": 10201610, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "getattr_count": 20792, + "lookup_base_count": 23, + "lookup_count": 41058, + "lookup_delta_count": 7550, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7058, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14920, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 12767153 + }, + "fuse": { + "fuse_adapter_attr_hits": 6001, + "fuse_adapter_attr_misses": 5637, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5540, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 30442, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11638, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12290, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11638, + "fuse_op_getattr_nanos": 81630705, + "fuse_op_lookup_count": 12290, + "fuse_op_lookup_nanos": 131860711, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 102197603, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 35818220, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 63788255, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2652664, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43536, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482 + } + }, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26185, + "attr_cache_misses": 30881, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33595, + "connection_wait_count": 33604, + "connection_wait_nanos": 10208367, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6001, + "fuse_adapter_attr_misses": 5637, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5540, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 30442, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11638, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12290, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11638, + "fuse_op_getattr_nanos": 81630705, + "fuse_op_lookup_count": 12290, + "fuse_op_lookup_nanos": 131860711, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 102197603, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 35818220, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 63788255, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2652664, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43536, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1858106, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20792, + "lookup_base_count": 23, + "lookup_count": 41058, + "lookup_delta_count": 7550, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7058, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14920, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 12934046 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26185, + "attr_cache_misses": 30881, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33595, + "connection_wait_count": 33604, + "connection_wait_nanos": 10208367, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "getattr_count": 20792, + "lookup_base_count": 23, + "lookup_count": 41058, + "lookup_delta_count": 7550, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7058, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14920, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 12934046 + }, + "fuse": { + "fuse_adapter_attr_hits": 6001, + "fuse_adapter_attr_misses": 5637, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5540, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 30442, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11638, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12290, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11638, + "fuse_op_getattr_nanos": 81630705, + "fuse_op_lookup_count": 12290, + "fuse_op_lookup_nanos": 131860711, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 102197603, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 35818220, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 63788255, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2652664, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43536, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1858106 + } + }, + "source": "cli" + } + ], + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-e09c5e33cc4249779c53f1e85f08fbe4", + "--no-default-allows", + "--", + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/agentfs-base", + "duration_seconds": 4.013545581008657, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 792864828, + "agentfs_batcher_commit_txns": 4780, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29045, + "base_fast_inode_invalidations": 15025, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create": 2, + "connection_reuse": 31198, + "connection_wait_count": 31200, + "connection_wait_nanos": 9492867, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 91, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5281, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21779, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 34, + "fuse_dispatch_wait_count": 34, + "fuse_dispatch_wait_nanos": 571446, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52448340, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5025, + "fuse_ino_file_resolutions": 18, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11698, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 460793278, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 31, + "fuse_op_forget_nanos": 913722, + "fuse_op_getattr_count": 5025, + "fuse_op_getattr_nanos": 50503736, + "fuse_op_lookup_count": 11698, + "fuse_op_lookup_nanos": 126133971, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 96227847, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 30116469, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2099847, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1115862338, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 25063983, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16147, + "fuse_read_lane_wait_nanos": 2404255, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32002, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1794011, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19330, + "lookup_base_count": 23, + "lookup_count": 40286, + "lookup_delta_count": 7190, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6774, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14254, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32899, + "path_resolution_count": 7117, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1649454 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15025, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 792864828, + "agentfs_batcher_commit_txns": 4780, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29045, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create": 2, + "connection_reuse": 31198, + "connection_wait_count": 31200, + "connection_wait_nanos": 9492867, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "getattr_count": 19330, + "lookup_base_count": 23, + "lookup_count": 40286, + "lookup_delta_count": 7190, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6774, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14254, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32899, + "path_resolution_count": 7117, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1649454 + }, + "fuse": { + "fuse_adapter_attr_hits": 91, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5281, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21779, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 34, + "fuse_dispatch_wait_count": 34, + "fuse_dispatch_wait_nanos": 571446, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52448340, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5025, + "fuse_ino_file_resolutions": 18, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11698, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 460793278, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 31, + "fuse_op_forget_nanos": 913722, + "fuse_op_getattr_count": 5025, + "fuse_op_getattr_nanos": 50503736, + "fuse_op_lookup_count": 11698, + "fuse_op_lookup_nanos": 126133971, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 96227847, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 30116469, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2099847, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1115862338, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 25063983, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16147, + "fuse_read_lane_wait_nanos": 2404255, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 32002, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1794011 + } + }, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 798649681, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26163, + "attr_cache_misses": 30820, + "base_fast_inode_invalidations": 15076, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 9, + "connection_reuse": 31823, + "connection_wait_count": 31832, + "connection_wait_nanos": 9785985, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4652, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27105, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 42, + "fuse_dispatch_wait_count": 42, + "fuse_dispatch_wait_nanos": 619581, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10282, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11746, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 462019443, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 39, + "fuse_op_forget_nanos": 995583, + "fuse_op_getattr_count": 10282, + "fuse_op_getattr_nanos": 80113033, + "fuse_op_lookup_count": 11746, + "fuse_op_lookup_nanos": 126853697, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 99355989, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 32436606, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3292533, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1120618479, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 25385864, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16907, + "fuse_read_lane_wait_nanos": 2526676, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37365, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1844053, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20750, + "lookup_base_count": 23, + "lookup_count": 40450, + "lookup_delta_count": 7250, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14340, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33023, + "path_resolution_count": 7162, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2493834 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15076, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 798649681, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26163, + "attr_cache_misses": 30820, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 9, + "connection_reuse": 31823, + "connection_wait_count": 31832, + "connection_wait_nanos": 9785985, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "getattr_count": 20750, + "lookup_base_count": 23, + "lookup_count": 40450, + "lookup_delta_count": 7250, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14340, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33023, + "path_resolution_count": 7162, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2493834 + }, + "fuse": { + "fuse_adapter_attr_hits": 4652, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27105, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 42, + "fuse_dispatch_wait_count": 42, + "fuse_dispatch_wait_nanos": 619581, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10282, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11746, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 462019443, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 39, + "fuse_op_forget_nanos": 995583, + "fuse_op_getattr_count": 10282, + "fuse_op_getattr_nanos": 80113033, + "fuse_op_lookup_count": 11746, + "fuse_op_lookup_nanos": 126853697, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 99355989, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 32436606, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3292533, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1120618479, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 25385864, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16907, + "fuse_read_lane_wait_nanos": 2526676, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 37365, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1844053 + } + }, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 802482989, + "agentfs_batcher_commit_txns": 4795, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26181, + "attr_cache_misses": 30855, + "base_fast_inode_invalidations": 15100, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33528, + "connection_wait_count": 33537, + "connection_wait_nanos": 10164289, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5527, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 29680, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11163, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12037, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11163, + "fuse_op_getattr_nanos": 81146864, + "fuse_op_lookup_count": 12037, + "fuse_op_lookup_nanos": 131535403, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 101182894, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 34645803, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1126636831, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 27294644, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2645916, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 42710, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1854794, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20774, + "lookup_base_count": 23, + "lookup_count": 41048, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14918, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11642244 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15100, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 802482989, + "agentfs_batcher_commit_txns": 4795, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26181, + "attr_cache_misses": 30855, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33528, + "connection_wait_count": 33537, + "connection_wait_nanos": 10164289, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "getattr_count": 20774, + "lookup_base_count": 23, + "lookup_count": 41048, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14918, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11642244 + }, + "fuse": { + "fuse_adapter_attr_hits": 5527, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 29680, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11163, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12037, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11163, + "fuse_op_getattr_nanos": 81146864, + "fuse_op_lookup_count": 12037, + "fuse_op_lookup_nanos": 131535403, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 101182894, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 34645803, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1126636831, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 27294644, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2645916, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 42710, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1854794 + } + }, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 802482989, + "agentfs_batcher_commit_txns": 4795, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26182, + "attr_cache_misses": 30855, + "base_fast_inode_invalidations": 15100, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33536, + "connection_wait_count": 33545, + "connection_wait_nanos": 10169872, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5596, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 29756, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11232, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11232, + "fuse_op_getattr_nanos": 81220965, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 131560467, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 101182894, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1126636831, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 27294644, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17904, + "fuse_read_lane_wait_nanos": 2646486, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 42786, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1854794, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20774, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11642244 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15100, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 802482989, + "agentfs_batcher_commit_txns": 4795, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26182, + "attr_cache_misses": 30855, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33536, + "connection_wait_count": 33545, + "connection_wait_nanos": 10169872, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20774, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11642244 + }, + "fuse": { + "fuse_adapter_attr_hits": 5596, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 29756, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11232, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11232, + "fuse_op_getattr_nanos": 81220965, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 131560467, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 101182894, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1126636831, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 27294644, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17904, + "fuse_read_lane_wait_nanos": 2646486, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 42786, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1854794 + } + }, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26182, + "attr_cache_misses": 30879, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33568, + "connection_wait_count": 33577, + "connection_wait_nanos": 10186541, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5604, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 29772, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11240, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11240, + "fuse_op_getattr_nanos": 81230429, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 131560467, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 102177108, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17912, + "fuse_read_lane_wait_nanos": 2649152, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 42818, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20790, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 12489741 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26182, + "attr_cache_misses": 30879, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33568, + "connection_wait_count": 33577, + "connection_wait_nanos": 10186541, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20790, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 12489741 + }, + "fuse": { + "fuse_adapter_attr_hits": 5604, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 29772, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11240, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11240, + "fuse_op_getattr_nanos": 81230429, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 131560467, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 102177108, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17912, + "fuse_read_lane_wait_nanos": 2649152, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 42818, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482 + } + }, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26182, + "attr_cache_misses": 30879, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33568, + "connection_wait_count": 33577, + "connection_wait_nanos": 10186541, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5957, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5306, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30139, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11593, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12052, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11593, + "fuse_op_getattr_nanos": 81551167, + "fuse_op_lookup_count": 12052, + "fuse_op_lookup_nanos": 131574896, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 102187457, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17912, + "fuse_read_lane_wait_nanos": 2649152, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43197, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20790, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6824, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 12489741 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26182, + "attr_cache_misses": 30879, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33568, + "connection_wait_count": 33577, + "connection_wait_nanos": 10186541, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20790, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6824, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 12489741 + }, + "fuse": { + "fuse_adapter_attr_hits": 5957, + "fuse_adapter_attr_misses": 5636, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5306, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30139, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11593, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12052, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11593, + "fuse_op_getattr_nanos": 81551167, + "fuse_op_lookup_count": 12052, + "fuse_op_lookup_nanos": 131574896, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 102187457, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 35452736, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 62849865, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17912, + "fuse_read_lane_wait_nanos": 2649152, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43197, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482 + } + }, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26185, + "attr_cache_misses": 30881, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33592, + "connection_wait_count": 33601, + "connection_wait_nanos": 10201610, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6001, + "fuse_adapter_attr_misses": 5637, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5540, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 30442, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11638, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12290, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11638, + "fuse_op_getattr_nanos": 81630705, + "fuse_op_lookup_count": 12290, + "fuse_op_lookup_nanos": 131860711, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 102197603, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 35818220, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 63788255, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2652664, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43536, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20792, + "lookup_base_count": 23, + "lookup_count": 41058, + "lookup_delta_count": 7550, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7058, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14920, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 12767153 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26185, + "attr_cache_misses": 30881, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33592, + "connection_wait_count": 33601, + "connection_wait_nanos": 10201610, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "getattr_count": 20792, + "lookup_base_count": 23, + "lookup_count": 41058, + "lookup_delta_count": 7550, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7058, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14920, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 12767153 + }, + "fuse": { + "fuse_adapter_attr_hits": 6001, + "fuse_adapter_attr_misses": 5637, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5540, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 30442, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11638, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12290, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11638, + "fuse_op_getattr_nanos": 81630705, + "fuse_op_lookup_count": 12290, + "fuse_op_lookup_nanos": 131860711, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 102197603, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 35818220, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 63788255, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2652664, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43536, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1856482 + } + }, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26185, + "attr_cache_misses": 30881, + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33595, + "connection_wait_count": 33604, + "connection_wait_nanos": 10208367, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6001, + "fuse_adapter_attr_misses": 5637, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5540, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 30442, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11638, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12290, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11638, + "fuse_op_getattr_nanos": 81630705, + "fuse_op_lookup_count": 12290, + "fuse_op_lookup_nanos": 131860711, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 102197603, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 35818220, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 63788255, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2652664, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43536, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1858106, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20792, + "lookup_base_count": 23, + "lookup_count": 41058, + "lookup_delta_count": 7550, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7058, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14920, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 12934046 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15116, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 803850964, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1351680, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26185, + "attr_cache_misses": 30881, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 9, + "connection_reuse": 33595, + "connection_wait_count": 33604, + "connection_wait_nanos": 10208367, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "getattr_count": 20792, + "lookup_base_count": 23, + "lookup_count": 41058, + "lookup_delta_count": 7550, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7058, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14920, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 12934046 + }, + "fuse": { + "fuse_adapter_attr_hits": 6001, + "fuse_adapter_attr_misses": 5637, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5540, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 30442, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 46, + "fuse_dispatch_wait_count": 46, + "fuse_dispatch_wait_nanos": 678961, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11638, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12290, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 462638918, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 708, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 1027314, + "fuse_op_getattr_count": 11638, + "fuse_op_getattr_nanos": 81630705, + "fuse_op_lookup_count": 12290, + "fuse_op_lookup_nanos": 131860711, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 930, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 102197603, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 35818220, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 63788255, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1129126267, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 27763323, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2652664, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43536, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1858106 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 64588, + "stderr_tail": "en_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":803850964,\"agentfs_batcher_commit_txns\":4803,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1351680,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26182,\"attr_cache_misses\":30879,\"chunk_read_chunks\":267,\"chunk_read_queries\":122,\"chunk_write_chunks\":1003,\"connection_create\":9,\"connection_reuse\":33568,\"connection_wait_count\":33577,\"connection_wait_nanos\":10186541,\"dentry_cache_hits\":26125,\"dentry_cache_misses\":14052,\"getattr_count\":20790,\"lookup_base_count\":23,\"lookup_count\":41050,\"lookup_delta_count\":7546,\"lookup_whiteout_count\":0,\"negative_cache_hits\":6824,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46085,\"negative_lookup_count\":14918,\"path_cache_hits\":26125,\"path_cache_misses\":14052,\"path_component_count\":37629,\"path_resolution_count\":8139,\"readdir_count\":1,\"readdir_plus_count\":1398,\"wal_checkpoint_count\":708,\"wal_checkpoint_nanos\":12489741},\"fuse\":{\"fuse_adapter_attr_hits\":5957,\"fuse_adapter_attr_misses\":5636,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6734,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5306,\"fuse_adapter_negative_misses\":6734,\"fuse_callback_count\":30139,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":46,\"fuse_dispatch_wait_count\":46,\"fuse_dispatch_wait_nanos\":678961,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":11593,\"fuse_ino_file_resolutions\":24,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12052,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":462638918,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":708,\"fuse_op_forget_count\":43,\"fuse_op_forget_nanos\":1027314,\"fuse_op_getattr_count\":11593,\"fuse_op_getattr_nanos\":81551167,\"fuse_op_lookup_count\":12052,\"fuse_op_lookup_nanos\":131574896,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":930,\"fuse_op_other_count\":3580,\"fuse_op_other_nanos\":102187457,\"fuse_op_read_count\":239,\"fuse_op_read_nanos\":35452736,\"fuse_op_readdirplus_count\":1407,\"fuse_op_readdirplus_nanos\":62849865,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1129126267,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":27763323,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":239,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17912,\"fuse_read_lane_wait_nanos\":2649152,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1407,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":43197,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1856482}},\"source\":\"phase-checkpoint-6\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":803850964,\"agentfs_batcher_commit_txns\":4803,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1351680,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26185,\"attr_cache_misses\":30881,\"base_fast_inode_invalidations\":15116,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create\":9,\"connection_reuse\":33592,\"connection_wait_count\":33601,\"connection_wait_nanos\":10201610,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":6001,\"fuse_adapter_attr_misses\":5637,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5540,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":30442,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":46,\"fuse_dispatch_wait_count\":46,\"fuse_dispatch_wait_nanos\":678961,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":11638,\"fuse_ino_file_resolutions\":25,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12290,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":462638918,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":708,\"fuse_op_forget_count\":43,\"fuse_op_forget_nanos\":1027314,\"fuse_op_getattr_count\":11638,\"fuse_op_getattr_nanos\":81630705,\"fuse_op_lookup_count\":12290,\"fuse_op_lookup_nanos\":131860711,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":930,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":102197603,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":35818220,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":63788255,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1129126267,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":27763323,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17927,\"fuse_read_lane_wait_nanos\":2652664,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":43536,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1856482,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20792,\"lookup_base_count\":23,\"lookup_count\":41058,\"lookup_delta_count\":7550,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7058,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14920,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37671,\"path_resolution_count\":8149,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":717,\"wal_checkpoint_nanos\":12767153},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":15116,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":803850964,\"agentfs_batcher_commit_txns\":4803,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1351680,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26185,\"attr_cache_misses\":30881,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create\":9,\"connection_reuse\":33592,\"connection_wait_count\":33601,\"connection_wait_nanos\":10201610,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"getattr_count\":20792,\"lookup_base_count\":23,\"lookup_count\":41058,\"lookup_delta_count\":7550,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7058,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14920,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37671,\"path_resolution_count\":8149,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":717,\"wal_checkpoint_nanos\":12767153},\"fuse\":{\"fuse_adapter_attr_hits\":6001,\"fuse_adapter_attr_misses\":5637,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5540,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":30442,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":46,\"fuse_dispatch_wait_count\":46,\"fuse_dispatch_wait_nanos\":678961,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":11638,\"fuse_ino_file_resolutions\":25,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12290,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":462638918,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":708,\"fuse_op_forget_count\":43,\"fuse_op_forget_nanos\":1027314,\"fuse_op_getattr_count\":11638,\"fuse_op_getattr_nanos\":81630705,\"fuse_op_lookup_count\":12290,\"fuse_op_lookup_nanos\":131860711,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":930,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":102197603,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":35818220,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":63788255,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1129126267,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":27763323,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17927,\"fuse_read_lane_wait_nanos\":2652664,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":43536,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1856482}},\"source\":\"phase-checkpoint-7\"}\n\nSession: git-workload-e09c5e33cc4249779c53f1e85f08fbe4\n\nTo resume this session:\n agentfs run --session git-workload-e09c5e33cc4249779c53f1e85f08fbe4\n\nTo see what changed:\n agentfs diff git-workload-e09c5e33cc4249779c53f1e85f08fbe4\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":803850964,\"agentfs_batcher_commit_txns\":4803,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1351680,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26185,\"attr_cache_misses\":30881,\"base_fast_inode_invalidations\":15116,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create\":9,\"connection_reuse\":33595,\"connection_wait_count\":33604,\"connection_wait_nanos\":10208367,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":6001,\"fuse_adapter_attr_misses\":5637,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5540,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":30442,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":46,\"fuse_dispatch_wait_count\":46,\"fuse_dispatch_wait_nanos\":678961,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":11638,\"fuse_ino_file_resolutions\":25,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12290,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":462638918,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":708,\"fuse_op_forget_count\":43,\"fuse_op_forget_nanos\":1027314,\"fuse_op_getattr_count\":11638,\"fuse_op_getattr_nanos\":81630705,\"fuse_op_lookup_count\":12290,\"fuse_op_lookup_nanos\":131860711,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":930,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":102197603,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":35818220,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":63788255,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1129126267,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":27763323,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17927,\"fuse_read_lane_wait_nanos\":2652664,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":43536,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1858106,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20792,\"lookup_base_count\":23,\"lookup_count\":41058,\"lookup_delta_count\":7550,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7058,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14920,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37671,\"path_resolution_count\":8149,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":719,\"wal_checkpoint_nanos\":12934046},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":15116,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":803850964,\"agentfs_batcher_commit_txns\":4803,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1351680,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26185,\"attr_cache_misses\":30881,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create\":9,\"connection_reuse\":33595,\"connection_wait_count\":33604,\"connection_wait_nanos\":10208367,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"getattr_count\":20792,\"lookup_base_count\":23,\"lookup_count\":41058,\"lookup_delta_count\":7550,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7058,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14920,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37671,\"path_resolution_count\":8149,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":719,\"wal_checkpoint_nanos\":12934046},\"fuse\":{\"fuse_adapter_attr_hits\":6001,\"fuse_adapter_attr_misses\":5637,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5540,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":30442,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":46,\"fuse_dispatch_wait_count\":46,\"fuse_dispatch_wait_nanos\":678961,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":11638,\"fuse_ino_file_resolutions\":25,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12290,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":462638918,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":708,\"fuse_op_forget_count\":43,\"fuse_op_forget_nanos\":1027314,\"fuse_op_getattr_count\":11638,\"fuse_op_getattr_nanos\":81630705,\"fuse_op_lookup_count\":12290,\"fuse_op_lookup_nanos\":131860711,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":930,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":102197603,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":35818220,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":63788255,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1129126267,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":27763323,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17927,\"fuse_read_lane_wait_nanos\":2652664,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":43536,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1858106}},\"source\":\"cli\"}\n", + "stdout_bytes": 18886, + "stdout_tail": "2026-07-04T01:14:05.232762Z INFO agentfs::fuser::session: resolved FUSE dispatch mode: parallel workers=7 queue_capacity=28\n2026-07-04T01:14:05.233277Z INFO agentfs::fuse: advertising FUSE_OVER_IO_URING\n2026-07-04T01:14:05.233346Z INFO agentfs::fuser::uring: starting fuse-over-io_uring queues nr_queues=14 depth=4\n2026-07-04T01:14:06.603577Z WARN agentfs::fuser::request: Request RequestId(26501): Failed to send reply: No such file or directory (os error 2)\n2026-07-04T01:14:07.603561Z WARN agentfs::fuser::request: Request RequestId(53005): Failed to send reply: No such file or directory (os error 2)\n{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.024519680009689182, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work\", \"duration_seconds\": 0.00708862098690588, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work\", \"duration_seconds\": 0.008762903991737403, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work\", \"duration_seconds\": 0.008617704006610438, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.004875056998571381, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work\", \"duration_seconds\": 0.20235960099671502, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work\", \"duration_seconds\": 0.05157221599074546, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/mirror.git\", \"/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/agentfs-base\", \"duration_seconds\": 2.7773549539997475, \"returncode\": 0, \"stderr_bytes\": 2307, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\nUpdating files: 38% (1803/4644)\\nUpdating files: 39% (1812/4644)\\nUpdating files: 40% (1858/4644)\\nUpdating files: 41% (1905/4644)\\nUpdating files: 42% (1951/4644)\\nUpdating files: 43% (1997/4644)\\nUpdating files: 44% (2044/4644)\\nUpdating files: 45% (2090/4644)\\nUpdating files: 46% (2137/4644)\\nUpdating files: 47% (2183/4644)\\nUpdating files: 48% (2230/4644)\\nUpdating files: 49% (2276/4644)\\nUpdating files: 50% (2322/4644)\\nUpdating files: 51% (2369/4644)\\nUpdating files: 52% (2415/4644)\\nUpdating files: 53% (2462/4644)\\nUpdating files: 54% (2508/4644)\\nUpdating files: 55% (2555/4644)\\nUpdating files: 56% (2601/4644)\\nUpdating files: 57% (2648/4644)\\nUpdating files: 58% (2694/4644)\\nUpdating files: 59% (2740/4644)\\nUpdating files: 60% (2787/4644)\\nUpdating files: 61% (2833/4644)\\nUpdating files: 62% (2880/4644)\\nUpdating files: 63% (2926/4644)\\nUpdating files: 64% (2973/4644)\\nUpdating files: 65% (3019/4644)\\nUpdating files: 66% (3066/4644)\\nUpdating files: 67% (3112/4644)\\nUpdating files: 68% (3158/4644)\\nUpdating files: 69% (3205/4644)\\nUpdating files: 70% (3251/4644)\\nUpdating files: 71% (3298/4644)\\nUpdating files: 72% (3344/4644)\\nUpdating files: 73% (3391/4644)\\nUpdating files: 74% (3437/4644)\\nUpdating files: 75% (3483/4644)\\nUpdating files: 76% (3530/4644)\\nUpdating files: 77% (3576/4644)\\nUpdating files: 78% (3623/4644)\\nUpdating files: 79% (3669/4644)\\nUpdating files: 80% (3716/4644)\\nUpdating files: 81% (3762/4644)\\nUpdating files: 82% (3809/4644)\\nUpdating files: 82% (3824/4644)\\nUpdating files: 83% (3855/4644)\\nUpdating files: 84% (3901/4644)\\nUpdating files: 85% (3948/4644)\\nUpdating files: 86% (3994/4644)\\nUpdating files: 87% (4041/4644)\\nUpdating files: 88% (4087/4644)\\nUpdating files: 89% (4134/4644)\\nUpdating files: 90% (4180/4644)\\nUpdating files: 91% (4227/4644)\\nUpdating files: 92% (4273/4644)\\nUpdating files: 93% (4319/4644)\\nUpdating files: 94% (4366/4644)\\nUpdating files: 95% (4412/4644)\\nUpdating files: 96% (4459/4644)\\nUpdating files: 97% (4505/4644)\\nUpdating files: 98% (4552/4644)\\nUpdating files: 99% (4598/4644)\\nUpdating files: 100% (4644/4644)\\nUpdating files: 100% (4644/4644), done.\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work\", \"duration_seconds\": 0.026326373001211323, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work\", \"duration_seconds\": 0.12444656799198128, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.05428599700098857, \"clone\": 2.777395411001635, \"diff\": 0.024519680009689182, \"edit\": 0.004875056998571381, \"fsck\": 0.2023740630102111, \"read_search\": 0.008785623009316623, \"status\": 0.1508024939976167}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work\", \"duration_seconds\": 0.0056938799971248955, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 3.9237991579866502}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.024519680009689182, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work", + "duration_seconds": 0.00708862098690588, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work", + "duration_seconds": 0.008762903991737403, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work", + "duration_seconds": 0.008617704006610438, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.004875056998571381, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work", + "duration_seconds": 0.20235960099671502, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work", + "duration_seconds": 0.05157221599074546, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/mirror.git", + "/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/agentfs-base", + "duration_seconds": 2.7773549539997475, + "returncode": 0, + "stderr_bytes": 2307, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\nUpdating files: 38% (1803/4644)\nUpdating files: 39% (1812/4644)\nUpdating files: 40% (1858/4644)\nUpdating files: 41% (1905/4644)\nUpdating files: 42% (1951/4644)\nUpdating files: 43% (1997/4644)\nUpdating files: 44% (2044/4644)\nUpdating files: 45% (2090/4644)\nUpdating files: 46% (2137/4644)\nUpdating files: 47% (2183/4644)\nUpdating files: 48% (2230/4644)\nUpdating files: 49% (2276/4644)\nUpdating files: 50% (2322/4644)\nUpdating files: 51% (2369/4644)\nUpdating files: 52% (2415/4644)\nUpdating files: 53% (2462/4644)\nUpdating files: 54% (2508/4644)\nUpdating files: 55% (2555/4644)\nUpdating files: 56% (2601/4644)\nUpdating files: 57% (2648/4644)\nUpdating files: 58% (2694/4644)\nUpdating files: 59% (2740/4644)\nUpdating files: 60% (2787/4644)\nUpdating files: 61% (2833/4644)\nUpdating files: 62% (2880/4644)\nUpdating files: 63% (2926/4644)\nUpdating files: 64% (2973/4644)\nUpdating files: 65% (3019/4644)\nUpdating files: 66% (3066/4644)\nUpdating files: 67% (3112/4644)\nUpdating files: 68% (3158/4644)\nUpdating files: 69% (3205/4644)\nUpdating files: 70% (3251/4644)\nUpdating files: 71% (3298/4644)\nUpdating files: 72% (3344/4644)\nUpdating files: 73% (3391/4644)\nUpdating files: 74% (3437/4644)\nUpdating files: 75% (3483/4644)\nUpdating files: 76% (3530/4644)\nUpdating files: 77% (3576/4644)\nUpdating files: 78% (3623/4644)\nUpdating files: 79% (3669/4644)\nUpdating files: 80% (3716/4644)\nUpdating files: 81% (3762/4644)\nUpdating files: 82% (3809/4644)\nUpdating files: 82% (3824/4644)\nUpdating files: 83% (3855/4644)\nUpdating files: 84% (3901/4644)\nUpdating files: 85% (3948/4644)\nUpdating files: 86% (3994/4644)\nUpdating files: 87% (4041/4644)\nUpdating files: 88% (4087/4644)\nUpdating files: 89% (4134/4644)\nUpdating files: 90% (4180/4644)\nUpdating files: 91% (4227/4644)\nUpdating files: 92% (4273/4644)\nUpdating files: 93% (4319/4644)\nUpdating files: 94% (4366/4644)\nUpdating files: 95% (4412/4644)\nUpdating files: 96% (4459/4644)\nUpdating files: 97% (4505/4644)\nUpdating files: 98% (4552/4644)\nUpdating files: 99% (4598/4644)\nUpdating files: 100% (4644/4644)\nUpdating files: 100% (4644/4644), done.\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work", + "duration_seconds": 0.026326373001211323, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work", + "duration_seconds": 0.12444656799198128, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.05428599700098857, + "clone": 2.777395411001635, + "diff": 0.024519680009689182, + "edit": 0.004875056998571381, + "fsck": 0.2023740630102111, + "read_search": 0.008785623009316623, + "status": 0.1508024939976167 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/agentfs-base/work", + "duration_seconds": 0.0056938799971248955, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 3.9237991579866502 + } + }, + "base_tree": { + "after": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "0c44ee911aea10f3ef10e46fab550fc98ee5df7371c76ffa1a749db083177adc", + "symlinks": 0 + }, + "before": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "0c44ee911aea10f3ef10e46fab550fc98ee5df7371c76ffa1a749db083177adc", + "symlinks": 0 + }, + "unchanged": true + }, + "benchmark": "phase7-git-workload", + "command": { + "agentfs_prefix": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-e09c5e33cc4249779c53f1e85f08fbe4", + "--no-default-allows", + "--" + ], + "argv": [ + "/home/ain3sh/factory/vfs/scripts/validation/git-workload-benchmark.py", + "--output", + "/tmp/vfs-val/misc-a-bench-rebaseline/run3.json.iterations/iter-02.json", + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "workload_argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ] + }, + "correctness": { + "agentfs_backup_verify": true, + "agentfs_base_unchanged": true, + "agentfs_db_inspectable": true, + "agentfs_integrity_require_portable": true, + "agentfs_no_nonempty_sidecars": true, + "agentfs_portable": true, + "agentfs_returncode_zero": true, + "equivalence": { + "agentfs": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + }, + "checked": true, + "equivalent": true, + "native": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + } + }, + "native_returncode_zero": true, + "passed": true, + "performance_passed": false + }, + "database": { + "after": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-bkhbczd5/home/.agentfs/run/git-workload-e09c5e33cc4249779c53f1e85f08fbe4/delta.db" + } + ], + "path": "/tmp/agentfs-git-workload-bkhbczd5/home/.agentfs/run/git-workload-e09c5e33cc4249779c53f1e85f08fbe4/delta.db", + "total_bytes": 57147392 + }, + "backup": { + "artifacts": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-bkhbczd5/git-workload-backup.db" + } + ], + "path": "/tmp/agentfs-git-workload-bkhbczd5/git-workload-backup.db", + "total_bytes": 57147392 + }, + "inspect": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "path": "/tmp/agentfs-git-workload-bkhbczd5/git-workload-backup.db", + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "backup", + "/tmp/agentfs-git-workload-bkhbczd5/home/.agentfs/run/git-workload-e09c5e33cc4249779c53f1e85f08fbe4/delta.db", + "/tmp/agentfs-git-workload-bkhbczd5/git-workload-backup.db", + "--verify" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5", + "duration_seconds": 2.6570647429907694, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 0, + "fuse_readdirplus_mode": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_workers_configured": 0, + "fuse_writeback_cache_enabled": 0 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "fuse": { + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 7355, + "stderr_tail": "{\"counters\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_mode\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_workers_configured\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0,\"fuse_writeback_cache_enabled\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":0,\"fuse_readdirplus_mode\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_workers_configured\":0,\"fuse_writeback_cache_enabled\":0},\"core\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"fuse\":{\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0}},\"source\":\"cli\"}\n", + "stdout_bytes": 241, + "stdout_tail": "Source: /tmp/agentfs-git-workload-bkhbczd5/home/.agentfs/run/git-workload-e09c5e33cc4249779c53f1e85f08fbe4/delta.db\nBackup: /tmp/agentfs-git-workload-bkhbczd5/git-workload-backup.db\nCheckpoint: complete\nCopy: complete\nVerification: complete\n", + "timed_out": false + } + }, + "inspect_after": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "integrity": { + "result": { + "checks": [ + { + "detail": "ok", + "name": "pragma.integrity_check", + "ok": true, + "violating_rows": null + }, + { + "detail": "present", + "name": "schema.table.fs_config", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_symlink", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.kv_store", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.tool_calls", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 0.5", + "name": "config.schema_version", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 65536", + "name": "config.chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 16384", + "name": "config.inline_threshold", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.kind_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_has_no_chunks", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunked_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_size_matches_blob", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.non_regular_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_reference_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunk_length_within_chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 1, expected 1", + "name": "namespace.root_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_is_directory", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_target_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_root_inode_has_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_names_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_directory_nlink_matches_dentries", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.directory_nlink_positive", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.rows_reference_symlink_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.inodes_have_rows", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable: no partial-origin rows", + "name": "overlay.portability_status", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable requirement satisfied", + "name": "overlay.require_portable", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_regular", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_sizes_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_paths_absolute", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_references_partial_origin", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_unique", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_index_in_range", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.whiteout_paths_absolute", + "ok": true, + "violating_rows": 0 + } + ], + "database": "/tmp/agentfs-git-workload-bkhbczd5/home/.agentfs/run/git-workload-e09c5e33cc4249779c53f1e85f08fbe4/delta.db", + "ok": true, + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true + }, + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "integrity", + "/tmp/agentfs-git-workload-bkhbczd5/home/.agentfs/run/git-workload-e09c5e33cc4249779c53f1e85f08fbe4/delta.db", + "--json", + "--require-portable" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5", + "duration_seconds": 2.7822104869992472, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 0, + "fuse_readdirplus_mode": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_workers_configured": 0, + "fuse_writeback_cache_enabled": 0 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "fuse": { + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 7355, + "stderr_tail": "{\"counters\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_mode\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_workers_configured\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0,\"fuse_writeback_cache_enabled\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":0,\"fuse_readdirplus_mode\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_workers_configured\":0,\"fuse_writeback_cache_enabled\":0},\"core\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"fuse\":{\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0}},\"source\":\"cli\"}\n", + "stdout_bytes": 6395, + "stdout_tail": "{\n \"database\": \"/tmp/agentfs-git-workload-bkhbczd5/home/.agentfs/run/git-workload-e09c5e33cc4249779c53f1e85f08fbe4/delta.db\",\n \"ok\": true,\n \"portable\": true,\n \"origin_backed\": false,\n \"partial_origin_rows\": 0,\n \"checks\": [\n {\n \"name\": \"pragma.integrity_check\",\n \"ok\": true,\n \"detail\": \"ok\",\n \"violating_rows\": null\n },\n {\n \"name\": \"schema.table.fs_config\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_inode\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_dentry\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_data\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_symlink\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.kv_store\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.tool_calls\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.schema_version\",\n \"ok\": true,\n \"detail\": \"found 0.5\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.chunk_size\",\n \"ok\": true,\n \"detail\": \"found 65536\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.inline_threshold\",\n \"ok\": true,\n \"detail\": \"found 16384\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.kind_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_has_no_chunks\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunked_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_size_matches_blob\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.non_regular_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_reference_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunk_length_within_chunk_size\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.root_inode\",\n \"ok\": true,\n \"detail\": \"found 1, expected 1\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_is_directory\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_target_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_root_inode_has_dentry\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_names_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_directory_nlink_matches_dentries\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.directory_nlink_positive\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.rows_reference_symlink_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.inodes_have_rows\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.portability_status\",\n \"ok\": true,\n \"detail\": \"portable: no partial-origin rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.require_portable\",\n \"ok\": true,\n \"detail\": \"portable requirement satisfied\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_regular\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_sizes_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_references_partial_origin\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_unique\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_index_in_range\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.whiteout_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n }\n ]\n}\n", + "timed_out": false + } + }, + "nonempty_sidecars": false + }, + "environment": { + "AGENTFS_BIN": "/home/ain3sh/factory/vfs/target/release/agentfs", + "AGENTFS_PROFILE": "1" + }, + "git_commit": "2b811f03493e861e5a7786592d25ac73b39e1aba", + "kept_temp": false, + "native": { + "run": { + "argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/native", + "duration_seconds": 0.936675634002313, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 15995, + "stdout_tail": "{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.014816572002018802, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/native/work\", \"duration_seconds\": 0.005024946993216872, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/native/work\", \"duration_seconds\": 0.0047310739901149645, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/native/work\", \"duration_seconds\": 0.005027128994697705, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.00037815999530721456, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/native/work\", \"duration_seconds\": 0.17410612499224953, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/native/work\", \"duration_seconds\": 0.16024740799912252, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-bkhbczd5/native/mirror.git\", \"/tmp/agentfs-git-workload-bkhbczd5/native/work\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/native\", \"duration_seconds\": 0.37205095699755475, \"returncode\": 0, \"stderr_bytes\": 149, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-bkhbczd5/native/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/native/work\", \"duration_seconds\": 0.010340622000512667, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/native/work\", \"duration_seconds\": 0.14510634299949743, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.16255415299383458, \"clone\": 0.37208808699506335, \"diff\": 0.014816572002018802, \"edit\": 0.00037815999530721456, \"fsck\": 0.1741181089892052, \"read_search\": 0.005955979009740986, \"status\": 0.15546543500386178}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-bkhbczd5/native/work\", \"duration_seconds\": 0.004471602995181456, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 0.8855433049902786}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.014816572002018802, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/native/work", + "duration_seconds": 0.005024946993216872, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/native/work", + "duration_seconds": 0.0047310739901149645, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/native/work", + "duration_seconds": 0.005027128994697705, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.00037815999530721456, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/native/work", + "duration_seconds": 0.17410612499224953, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/native/work", + "duration_seconds": 0.16024740799912252, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-bkhbczd5/native/mirror.git", + "/tmp/agentfs-git-workload-bkhbczd5/native/work" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/native", + "duration_seconds": 0.37205095699755475, + "returncode": 0, + "stderr_bytes": 149, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-bkhbczd5/native/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/native/work", + "duration_seconds": 0.010340622000512667, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/native/work", + "duration_seconds": 0.14510634299949743, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.16255415299383458, + "clone": 0.37208808699506335, + "diff": 0.014816572002018802, + "edit": 0.00037815999530721456, + "fsck": 0.1741181089892052, + "read_search": 0.005955979009740986, + "status": 0.15546543500386178 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-bkhbczd5/native/work", + "duration_seconds": 0.004471602995181456, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 0.8855433049902786 + } + }, + "parameters": { + "edit_files": 8, + "fixture_dirs": 8, + "fixture_file_size_bytes": 1024, + "fixture_files": 96, + "read_bytes": 4096, + "read_files": 64, + "search_token": "AGENTFS_TOKEN", + "skip_fsck": false, + "timeout_seconds": 180.0 + }, + "schema_version": 1, + "source": { + "kind": "source", + "mirror_head": "7d47056ea42636271ac020b86347fbbef49490aa", + "path": "/home/ain3sh/factory/vfs/.agents/benchmarks/fixtures/codex" + }, + "summary": { + "agentfs_base_unchanged": true, + "agentfs_seconds": 3.9237991579866502, + "all_equivalent": true, + "correctness_passed": true, + "native_seconds": 0.8855433049902786, + "passed": true, + "performance_passed": false, + "phase_ratios": { + "checkout": { + "agentfs_seconds": 0.05428599700098857, + "native_seconds": 0.16255415299383458, + "ratio": 0.33395638315711046 + }, + "clone": { + "agentfs_seconds": 2.777395411001635, + "native_seconds": 0.37208808699506335, + "ratio": 7.464349190621853 + }, + "diff": { + "agentfs_seconds": 0.024519680009689182, + "native_seconds": 0.014816572002018802, + "ratio": 1.6548821148608668 + }, + "edit": { + "agentfs_seconds": 0.004875056998571381, + "native_seconds": 0.00037815999530721456, + "ratio": 12.891519618861109 + }, + "fsck": { + "agentfs_seconds": 0.2023740630102111, + "native_seconds": 0.1741181089892052, + "ratio": 1.1622803864861504 + }, + "read_search": { + "agentfs_seconds": 0.008785623009316623, + "native_seconds": 0.005955979009740986, + "ratio": 1.475093010728843 + }, + "status": { + "agentfs_seconds": 0.1508024939976167, + "native_seconds": 0.15546543500386178, + "ratio": 0.9700065740907021 + } + }, + "ratio": 4.4309511865483815, + "threshold_failures": [ + { + "agentfs_seconds": 2.777395411001635, + "native_seconds": 0.37208808699506335, + "phase": "clone", + "ratio": 7.464349190621853 + }, + { + "agentfs_seconds": 0.004875056998571381, + "native_seconds": 0.00037815999530721456, + "phase": "edit", + "ratio": 12.891519618861109 + } + ] + }, + "temp_dir": "/tmp/agentfs-git-workload-bkhbczd5" +} diff --git a/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-03.json b/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-03.json new file mode 100644 index 00000000..45214ee3 --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-03.json @@ -0,0 +1,8674 @@ +{ + "agentfs": { + "bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "db_path": "/tmp/agentfs-git-workload-hulvm8vq/home/.agentfs/run/git-workload-5199c09fd6244c128bcf18f1942ccf0b/delta.db", + "per_phase_counters": { + "checkpoint_count": 7, + "label_count": 7, + "labels_aligned": true, + "phases": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 782562379, + "agentfs_batcher_commit_txns": 4777, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 971, + "connection_create": 2, + "connection_reuse": 31203, + "connection_wait_count": 31205, + "connection_wait_nanos": 9319037, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 103, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5278, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21788, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 37, + "fuse_dispatch_wait_count": 37, + "fuse_dispatch_wait_nanos": 402826, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52580350, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5037, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11695, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 465601091, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 34, + "fuse_op_forget_nanos": 977357, + "fuse_op_getattr_count": 5037, + "fuse_op_getattr_nanos": 50738616, + "fuse_op_lookup_count": 11695, + "fuse_op_lookup_nanos": 126434122, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 90094734, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 29135632, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1783658, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1103958669, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 26790926, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 2330202, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32011, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1865087, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6772, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1426999 + }, + "phase": "clone", + "seq": 1 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 2, + "agentfs_batcher_commit_latency_ns_total": 3165915, + "agentfs_batcher_commit_txns": 10, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 5, + "agentfs_batcher_drains_timer": 1, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 13, + "attr_cache_hits": 259, + "attr_cache_misses": 1789, + "base_fast_inode_invalidations": 51, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 12, + "connection_create": 4, + "connection_reuse": 626, + "connection_wait_count": 630, + "connection_wait_nanos": 250762, + "dentry_cache_hits": 73, + "dentry_cache_misses": 56, + "fuse_adapter_attr_hits": 4560, + "fuse_adapter_attr_misses": 712, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 35, + "fuse_adapter_inval_entry_notifications": 10, + "fuse_adapter_inval_inode_notifications": 28, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 13, + "fuse_adapter_negative_misses": 35, + "fuse_callback_count": 5343, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 5, + "fuse_dispatch_parallel_tasks": 7, + "fuse_dispatch_wait_count": 7, + "fuse_dispatch_wait_nanos": 219062, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 653926, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 5272, + "fuse_ino_file_resolutions": 3, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 50, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 9, + "fuse_op_create_nanos": 1059224, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 7, + "fuse_op_forget_nanos": 133182, + "fuse_op_getattr_count": 5272, + "fuse_op_getattr_nanos": 18354793, + "fuse_op_lookup_count": 50, + "fuse_op_lookup_nanos": 835243, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 15, + "fuse_op_other_nanos": 2695542, + "fuse_op_read_count": 8, + "fuse_op_read_nanos": 959708, + "fuse_op_readdirplus_count": 4, + "fuse_op_readdirplus_nanos": 1298775, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 14, + "fuse_op_setattr_nanos": 3311768, + "fuse_op_write_count": 9, + "fuse_op_write_nanos": 180231, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 8, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 777, + "fuse_read_lane_wait_nanos": 169368, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 4, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5381, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 591393, + "fuse_write_count": 9, + "fuse_write_lane_wait_count": 42, + "fuse_write_lane_wait_nanos": 74463, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 1452, + "lookup_base_count": 0, + "lookup_count": 164, + "lookup_delta_count": 60, + "lookup_whiteout_count": 0, + "negative_cache_hits": 34, + "negative_cache_invalidations": 26, + "negative_cache_misses": 148, + "negative_lookup_count": 86, + "path_cache_hits": 73, + "path_cache_misses": 56, + "path_component_count": 124, + "path_resolution_count": 45, + "readdir_count": 0, + "readdir_plus_count": 4, + "wal_checkpoint_count": 2, + "wal_checkpoint_nanos": 892665 + }, + "phase": "checkout", + "seq": 2 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 4, + "agentfs_batcher_commit_latency_ns_total": 3350580, + "agentfs_batcher_commit_txns": 4, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 2, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 6, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 4, + "attr_cache_hits": 19, + "attr_cache_misses": 33, + "base_fast_inode_invalidations": 24, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 20, + "chunk_read_queries": 12, + "chunk_write_chunks": 20, + "connection_create": 0, + "connection_reuse": 1704, + "connection_wait_count": 1704, + "connection_wait_nanos": 450326, + "dentry_cache_hits": 19, + "dentry_cache_misses": 570, + "fuse_adapter_attr_hits": 1375, + "fuse_adapter_attr_misses": 6, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 289, + "fuse_adapter_inval_entry_notifications": 4, + "fuse_adapter_inval_inode_notifications": 8, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 1, + "fuse_adapter_negative_misses": 289, + "fuse_callback_count": 3076, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 3, + "fuse_dispatch_wait_count": 3, + "fuse_dispatch_wait_nanos": 71839, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 1181458, + "fuse_flush_count": 6, + "fuse_flush_ranges": 6, + "fuse_getattr_count": 1381, + "fuse_ino_file_resolutions": 2, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 292, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 2, + "fuse_op_create_nanos": 751668, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 3, + "fuse_op_forget_nanos": 51063, + "fuse_op_getattr_count": 1381, + "fuse_op_getattr_nanos": 1535770, + "fuse_op_lookup_count": 292, + "fuse_op_lookup_nanos": 5289729, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 2762, + "fuse_op_other_nanos": 2034314, + "fuse_op_read_count": 12, + "fuse_op_read_nanos": 3537414, + "fuse_op_readdirplus_count": 1381, + "fuse_op_readdirplus_nanos": 69494791, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 6, + "fuse_op_setattr_nanos": 6028982, + "fuse_op_write_count": 10, + "fuse_op_write_nanos": 1592580, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 12, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 995, + "fuse_read_lane_wait_nanos": 125711, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1381, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5846, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 1181458, + "fuse_write_count": 10, + "fuse_write_lane_wait_count": 14, + "fuse_write_lane_wait_nanos": 16659, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 24, + "lookup_base_count": 0, + "lookup_count": 598, + "lookup_delta_count": 295, + "lookup_whiteout_count": 0, + "negative_cache_hits": 5, + "negative_cache_invalidations": 4, + "negative_cache_misses": 873, + "negative_lookup_count": 578, + "path_cache_hits": 19, + "path_cache_misses": 570, + "path_component_count": 4606, + "path_resolution_count": 977, + "readdir_count": 0, + "readdir_plus_count": 1376, + "wal_checkpoint_count": 688, + "wal_checkpoint_nanos": 11762064 + }, + "phase": "status", + "seq": 3 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 1, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 8, + "connection_wait_count": 8, + "connection_wait_nanos": 4205, + "dentry_cache_hits": 1, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 69, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 1, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 1, + "fuse_callback_count": 76, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 69, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 1, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 69, + "fuse_op_getattr_nanos": 49972, + "fuse_op_lookup_count": 1, + "fuse_op_lookup_nanos": 25152, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 6, + "fuse_op_read_nanos": 778407, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 6, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 2, + "fuse_read_lane_wait_nanos": 877, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 76, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 2, + "lookup_delta_count": 1, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 2, + "negative_lookup_count": 0, + "path_cache_hits": 1, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "read_search", + "seq": 4 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 1341052, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 8, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 8, + "attr_cache_hits": 0, + "attr_cache_misses": 24, + "base_fast_inode_invalidations": 16, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 32, + "connection_wait_count": 32, + "connection_wait_nanos": 13814, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 16, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 24, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 6398, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 16, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 16, + "fuse_op_getattr_nanos": 23829, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 8, + "fuse_op_other_nanos": 986555, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 8, + "fuse_op_setattr_nanos": 2407236, + "fuse_op_write_count": 8, + "fuse_op_write_nanos": 450087, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 8, + "fuse_read_lane_wait_nanos": 2100, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 40, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 6398, + "fuse_write_count": 8, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 1789, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 16, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 8, + "wal_checkpoint_nanos": 845859 + }, + "phase": "edit", + "seq": 5 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 1849, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 89, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 1938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 1849, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 89, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 1849, + "fuse_op_getattr_nanos": 1125750, + "fuse_op_lookup_count": 89, + "fuse_op_lookup_nanos": 101209, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 12, + "fuse_op_other_nanos": 8997, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 1950, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 89, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "diff", + "seq": 6 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 3, + "attr_cache_misses": 2, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 1, + "chunk_read_queries": 1, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 24, + "connection_wait_count": 24, + "connection_wait_nanos": 19473, + "dentry_cache_hits": 4, + "dentry_cache_misses": 2, + "fuse_adapter_attr_hits": 44, + "fuse_adapter_attr_misses": 1, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 4, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 248, + "fuse_adapter_negative_misses": 4, + "fuse_callback_count": 317, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 45, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 252, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 45, + "fuse_op_getattr_nanos": 79697, + "fuse_op_lookup_count": 252, + "fuse_op_lookup_nanos": 390015, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 36, + "fuse_op_other_nanos": 12772, + "fuse_op_read_count": 2, + "fuse_op_read_nanos": 319728, + "fuse_op_readdirplus_count": 18, + "fuse_op_readdirplus_nanos": 1113658, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 2, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 15, + "fuse_read_lane_wait_nanos": 5219, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 18, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 353, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 2, + "lookup_base_count": 0, + "lookup_count": 8, + "lookup_delta_count": 4, + "lookup_whiteout_count": 0, + "negative_cache_hits": 248, + "negative_cache_invalidations": 0, + "negative_cache_misses": 9, + "negative_lookup_count": 2, + "path_cache_hits": 4, + "path_cache_misses": 2, + "path_component_count": 42, + "path_resolution_count": 10, + "readdir_count": 0, + "readdir_plus_count": 18, + "wal_checkpoint_count": 9, + "wal_checkpoint_nanos": 341330 + }, + "phase": "fsck", + "seq": 7 + } + ] + }, + "profile_counters": { + "last_by_source": { + "cli": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26204, + "attr_cache_misses": 30901, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33600, + "connection_wait_count": 33606, + "connection_wait_nanos": 10063677, + "dentry_cache_hits": 26128, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8016, + "fuse_adapter_attr_misses": 5653, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5629, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32562, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13669, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12379, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13669, + "fuse_op_getattr_nanos": 71908427, + "fuse_op_lookup_count": 12379, + "fuse_op_lookup_nanos": 133075470, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 95832914, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34730889, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 73690882, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 2633477, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45657, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1959361, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7148, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26128, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 15442194 + }, + "phase-checkpoint-1": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 782562379, + "agentfs_batcher_commit_txns": 4777, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 971, + "connection_create": 2, + "connection_reuse": 31203, + "connection_wait_count": 31205, + "connection_wait_nanos": 9319037, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 103, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5278, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21788, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 37, + "fuse_dispatch_wait_count": 37, + "fuse_dispatch_wait_nanos": 402826, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52580350, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5037, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11695, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 465601091, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 34, + "fuse_op_forget_nanos": 977357, + "fuse_op_getattr_count": 5037, + "fuse_op_getattr_nanos": 50738616, + "fuse_op_lookup_count": 11695, + "fuse_op_lookup_nanos": 126434122, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 90094734, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 29135632, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1783658, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1103958669, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 26790926, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 2330202, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32011, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1865087, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6772, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1426999 + }, + "phase-checkpoint-2": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 785728294, + "agentfs_batcher_commit_txns": 4787, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4739, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9398, + "attr_cache_hits": 26181, + "attr_cache_misses": 30842, + "base_fast_inode_invalidations": 15079, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 6, + "connection_reuse": 31829, + "connection_wait_count": 31835, + "connection_wait_nanos": 9569799, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4663, + "fuse_adapter_attr_misses": 5646, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5291, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27131, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 621888, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4739, + "fuse_flush_ranges": 4739, + "fuse_getattr_count": 10309, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11745, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 466660315, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 41, + "fuse_op_forget_nanos": 1110539, + "fuse_op_getattr_count": 10309, + "fuse_op_getattr_nanos": 69093409, + "fuse_op_lookup_count": 11745, + "fuse_op_lookup_nanos": 127269365, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 92790276, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 30095340, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3082433, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1107270437, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 26971157, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16930, + "fuse_read_lane_wait_nanos": 2499570, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37392, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1939550, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2319664 + }, + "phase-checkpoint-3": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 789078874, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26200, + "attr_cache_misses": 30875, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33533, + "connection_wait_count": 33539, + "connection_wait_nanos": 10020125, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6038, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30207, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11690, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12037, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11690, + "fuse_op_getattr_nanos": 70629179, + "fuse_op_lookup_count": 12037, + "fuse_op_lookup_nanos": 132559094, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 94824590, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 33632754, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1113299419, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 28563737, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17925, + "fuse_read_lane_wait_nanos": 2625281, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43238, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1956209, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20812, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26123, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14081728 + }, + "phase-checkpoint-4": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 789078874, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26201, + "attr_cache_misses": 30875, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33541, + "connection_wait_count": 33547, + "connection_wait_nanos": 10024330, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6107, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30283, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11759, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11759, + "fuse_op_getattr_nanos": 70679151, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 132584246, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 94824590, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1113299419, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 28563737, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2626158, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43314, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1956209, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20812, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14081728 + }, + "phase-checkpoint-5": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26201, + "attr_cache_misses": 30899, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33573, + "connection_wait_count": 33579, + "connection_wait_nanos": 10038144, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6123, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30307, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 11775, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11775, + "fuse_op_getattr_nanos": 70702980, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 132584246, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 95811145, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17935, + "fuse_read_lane_wait_nanos": 2628258, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20828, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14927587 + }, + "phase-checkpoint-6": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26201, + "attr_cache_misses": 30899, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33573, + "connection_wait_count": 33579, + "connection_wait_nanos": 10038144, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7972, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5381, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32245, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13624, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12127, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13624, + "fuse_op_getattr_nanos": 71828730, + "fuse_op_lookup_count": 12127, + "fuse_op_lookup_nanos": 132685455, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 95820142, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17935, + "fuse_read_lane_wait_nanos": 2628258, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45304, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20828, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6900, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14927587 + }, + "phase-checkpoint-7": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26204, + "attr_cache_misses": 30901, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33597, + "connection_wait_count": 33603, + "connection_wait_nanos": 10057617, + "dentry_cache_hits": 26128, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8016, + "fuse_adapter_attr_misses": 5653, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5629, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32562, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13669, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12379, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13669, + "fuse_op_getattr_nanos": 71908427, + "fuse_op_lookup_count": 12379, + "fuse_op_lookup_nanos": 133075470, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 95832914, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34730889, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 73690882, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 2633477, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45657, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7148, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26128, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 15268917 + } + }, + "max_counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26204, + "attr_cache_misses": 30901, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33600, + "connection_wait_count": 33606, + "connection_wait_nanos": 10063677, + "dentry_cache_hits": 26128, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8016, + "fuse_adapter_attr_misses": 5653, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5629, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32562, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13669, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12379, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13669, + "fuse_op_getattr_nanos": 71908427, + "fuse_op_lookup_count": 12379, + "fuse_op_lookup_nanos": 133075470, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 95832914, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34730889, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 73690882, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 2633477, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45657, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1959361, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7148, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26128, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 15442194 + }, + "summary_count": 8 + }, + "profile_enabled": true, + "profile_summary_count": 8, + "session": "git-workload-5199c09fd6244c128bcf18f1942ccf0b" + }, + "agentfs_overlay": { + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 782562379, + "agentfs_batcher_commit_txns": 4777, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 971, + "connection_create": 2, + "connection_reuse": 31203, + "connection_wait_count": 31205, + "connection_wait_nanos": 9319037, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 103, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5278, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21788, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 37, + "fuse_dispatch_wait_count": 37, + "fuse_dispatch_wait_nanos": 402826, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52580350, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5037, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11695, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 465601091, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 34, + "fuse_op_forget_nanos": 977357, + "fuse_op_getattr_count": 5037, + "fuse_op_getattr_nanos": 50738616, + "fuse_op_lookup_count": 11695, + "fuse_op_lookup_nanos": 126434122, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 90094734, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 29135632, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1783658, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1103958669, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 26790926, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 2330202, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32011, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1865087, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6772, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1426999 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 782562379, + "agentfs_batcher_commit_txns": 4777, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 971, + "connection_create": 2, + "connection_reuse": 31203, + "connection_wait_count": 31205, + "connection_wait_nanos": 9319037, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6772, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1426999 + }, + "fuse": { + "fuse_adapter_attr_hits": 103, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5278, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21788, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 37, + "fuse_dispatch_wait_count": 37, + "fuse_dispatch_wait_nanos": 402826, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52580350, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5037, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11695, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 465601091, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 34, + "fuse_op_forget_nanos": 977357, + "fuse_op_getattr_count": 5037, + "fuse_op_getattr_nanos": 50738616, + "fuse_op_lookup_count": 11695, + "fuse_op_lookup_nanos": 126434122, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 90094734, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 29135632, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1783658, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1103958669, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 26790926, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 2330202, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 32011, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1865087 + } + }, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 785728294, + "agentfs_batcher_commit_txns": 4787, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4739, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9398, + "attr_cache_hits": 26181, + "attr_cache_misses": 30842, + "base_fast_inode_invalidations": 15079, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 6, + "connection_reuse": 31829, + "connection_wait_count": 31835, + "connection_wait_nanos": 9569799, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4663, + "fuse_adapter_attr_misses": 5646, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5291, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27131, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 621888, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4739, + "fuse_flush_ranges": 4739, + "fuse_getattr_count": 10309, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11745, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 466660315, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 41, + "fuse_op_forget_nanos": 1110539, + "fuse_op_getattr_count": 10309, + "fuse_op_getattr_nanos": 69093409, + "fuse_op_lookup_count": 11745, + "fuse_op_lookup_nanos": 127269365, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 92790276, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 30095340, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3082433, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1107270437, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 26971157, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16930, + "fuse_read_lane_wait_nanos": 2499570, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37392, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1939550, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2319664 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15079, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 785728294, + "agentfs_batcher_commit_txns": 4787, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4739, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9398, + "attr_cache_hits": 26181, + "attr_cache_misses": 30842, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 6, + "connection_reuse": 31829, + "connection_wait_count": 31835, + "connection_wait_nanos": 9569799, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2319664 + }, + "fuse": { + "fuse_adapter_attr_hits": 4663, + "fuse_adapter_attr_misses": 5646, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5291, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27131, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 621888, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4739, + "fuse_flush_ranges": 4739, + "fuse_getattr_count": 10309, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11745, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 466660315, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 41, + "fuse_op_forget_nanos": 1110539, + "fuse_op_getattr_count": 10309, + "fuse_op_getattr_nanos": 69093409, + "fuse_op_lookup_count": 11745, + "fuse_op_lookup_nanos": 127269365, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 92790276, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 30095340, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3082433, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1107270437, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 26971157, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16930, + "fuse_read_lane_wait_nanos": 2499570, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 37392, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1939550 + } + }, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 789078874, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26200, + "attr_cache_misses": 30875, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33533, + "connection_wait_count": 33539, + "connection_wait_nanos": 10020125, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6038, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30207, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11690, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12037, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11690, + "fuse_op_getattr_nanos": 70629179, + "fuse_op_lookup_count": 12037, + "fuse_op_lookup_nanos": 132559094, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 94824590, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 33632754, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1113299419, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 28563737, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17925, + "fuse_read_lane_wait_nanos": 2625281, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43238, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1956209, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20812, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26123, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14081728 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 789078874, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26200, + "attr_cache_misses": 30875, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33533, + "connection_wait_count": 33539, + "connection_wait_nanos": 10020125, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14052, + "getattr_count": 20812, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26123, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14081728 + }, + "fuse": { + "fuse_adapter_attr_hits": 6038, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30207, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11690, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12037, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11690, + "fuse_op_getattr_nanos": 70629179, + "fuse_op_lookup_count": 12037, + "fuse_op_lookup_nanos": 132559094, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 94824590, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 33632754, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1113299419, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 28563737, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17925, + "fuse_read_lane_wait_nanos": 2625281, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43238, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1956209 + } + }, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 789078874, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26201, + "attr_cache_misses": 30875, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33541, + "connection_wait_count": 33547, + "connection_wait_nanos": 10024330, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6107, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30283, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11759, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11759, + "fuse_op_getattr_nanos": 70679151, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 132584246, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 94824590, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1113299419, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 28563737, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2626158, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43314, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1956209, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20812, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14081728 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 789078874, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26201, + "attr_cache_misses": 30875, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33541, + "connection_wait_count": 33547, + "connection_wait_nanos": 10024330, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "getattr_count": 20812, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14081728 + }, + "fuse": { + "fuse_adapter_attr_hits": 6107, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30283, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11759, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11759, + "fuse_op_getattr_nanos": 70679151, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 132584246, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 94824590, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1113299419, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 28563737, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2626158, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43314, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1956209 + } + }, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26201, + "attr_cache_misses": 30899, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33573, + "connection_wait_count": 33579, + "connection_wait_nanos": 10038144, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6123, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30307, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 11775, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11775, + "fuse_op_getattr_nanos": 70702980, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 132584246, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 95811145, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17935, + "fuse_read_lane_wait_nanos": 2628258, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20828, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14927587 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26201, + "attr_cache_misses": 30899, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33573, + "connection_wait_count": 33579, + "connection_wait_nanos": 10038144, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "getattr_count": 20828, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14927587 + }, + "fuse": { + "fuse_adapter_attr_hits": 6123, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30307, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 11775, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11775, + "fuse_op_getattr_nanos": 70702980, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 132584246, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 95811145, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17935, + "fuse_read_lane_wait_nanos": 2628258, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43354, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998 + } + }, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26201, + "attr_cache_misses": 30899, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33573, + "connection_wait_count": 33579, + "connection_wait_nanos": 10038144, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7972, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5381, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32245, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13624, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12127, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13624, + "fuse_op_getattr_nanos": 71828730, + "fuse_op_lookup_count": 12127, + "fuse_op_lookup_nanos": 132685455, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 95820142, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17935, + "fuse_read_lane_wait_nanos": 2628258, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45304, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20828, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6900, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14927587 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26201, + "attr_cache_misses": 30899, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33573, + "connection_wait_count": 33579, + "connection_wait_nanos": 10038144, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "getattr_count": 20828, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6900, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14927587 + }, + "fuse": { + "fuse_adapter_attr_hits": 7972, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5381, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32245, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13624, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12127, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13624, + "fuse_op_getattr_nanos": 71828730, + "fuse_op_lookup_count": 12127, + "fuse_op_lookup_nanos": 132685455, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 95820142, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17935, + "fuse_read_lane_wait_nanos": 2628258, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45304, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998 + } + }, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26204, + "attr_cache_misses": 30901, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33597, + "connection_wait_count": 33603, + "connection_wait_nanos": 10057617, + "dentry_cache_hits": 26128, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8016, + "fuse_adapter_attr_misses": 5653, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5629, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32562, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13669, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12379, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13669, + "fuse_op_getattr_nanos": 71908427, + "fuse_op_lookup_count": 12379, + "fuse_op_lookup_nanos": 133075470, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 95832914, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34730889, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 73690882, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 2633477, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45657, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7148, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26128, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 15268917 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26204, + "attr_cache_misses": 30901, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33597, + "connection_wait_count": 33603, + "connection_wait_nanos": 10057617, + "dentry_cache_hits": 26128, + "dentry_cache_misses": 14054, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7148, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26128, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 15268917 + }, + "fuse": { + "fuse_adapter_attr_hits": 8016, + "fuse_adapter_attr_misses": 5653, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5629, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32562, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13669, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12379, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13669, + "fuse_op_getattr_nanos": 71908427, + "fuse_op_lookup_count": 12379, + "fuse_op_lookup_nanos": 133075470, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 95832914, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34730889, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 73690882, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 2633477, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45657, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998 + } + }, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26204, + "attr_cache_misses": 30901, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33600, + "connection_wait_count": 33606, + "connection_wait_nanos": 10063677, + "dentry_cache_hits": 26128, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8016, + "fuse_adapter_attr_misses": 5653, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5629, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32562, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13669, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12379, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13669, + "fuse_op_getattr_nanos": 71908427, + "fuse_op_lookup_count": 12379, + "fuse_op_lookup_nanos": 133075470, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 95832914, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34730889, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 73690882, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 2633477, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45657, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1959361, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7148, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26128, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 15442194 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26204, + "attr_cache_misses": 30901, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33600, + "connection_wait_count": 33606, + "connection_wait_nanos": 10063677, + "dentry_cache_hits": 26128, + "dentry_cache_misses": 14054, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7148, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26128, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 15442194 + }, + "fuse": { + "fuse_adapter_attr_hits": 8016, + "fuse_adapter_attr_misses": 5653, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5629, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32562, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13669, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12379, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13669, + "fuse_op_getattr_nanos": 71908427, + "fuse_op_lookup_count": 12379, + "fuse_op_lookup_nanos": 133075470, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 95832914, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34730889, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 73690882, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 2633477, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45657, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1959361 + } + }, + "source": "cli" + } + ], + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-5199c09fd6244c128bcf18f1942ccf0b", + "--no-default-allows", + "--", + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/agentfs-base", + "duration_seconds": 4.100164098999812, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 782562379, + "agentfs_batcher_commit_txns": 4777, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 971, + "connection_create": 2, + "connection_reuse": 31203, + "connection_wait_count": 31205, + "connection_wait_nanos": 9319037, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 103, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5278, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21788, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 37, + "fuse_dispatch_wait_count": 37, + "fuse_dispatch_wait_nanos": 402826, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52580350, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5037, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11695, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 465601091, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 34, + "fuse_op_forget_nanos": 977357, + "fuse_op_getattr_count": 5037, + "fuse_op_getattr_nanos": 50738616, + "fuse_op_lookup_count": 11695, + "fuse_op_lookup_nanos": 126434122, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 90094734, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 29135632, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1783658, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1103958669, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 26790926, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 2330202, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32011, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1865087, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6772, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1426999 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 782562379, + "agentfs_batcher_commit_txns": 4777, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 971, + "connection_create": 2, + "connection_reuse": 31203, + "connection_wait_count": 31205, + "connection_wait_nanos": 9319037, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6772, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1426999 + }, + "fuse": { + "fuse_adapter_attr_hits": 103, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5278, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21788, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 37, + "fuse_dispatch_wait_count": 37, + "fuse_dispatch_wait_nanos": 402826, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52580350, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5037, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11695, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 465601091, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 34, + "fuse_op_forget_nanos": 977357, + "fuse_op_getattr_count": 5037, + "fuse_op_getattr_nanos": 50738616, + "fuse_op_lookup_count": 11695, + "fuse_op_lookup_nanos": 126434122, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 90094734, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 29135632, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1783658, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1103958669, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 26790926, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 2330202, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 32011, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1865087 + } + }, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 785728294, + "agentfs_batcher_commit_txns": 4787, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4739, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9398, + "attr_cache_hits": 26181, + "attr_cache_misses": 30842, + "base_fast_inode_invalidations": 15079, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 6, + "connection_reuse": 31829, + "connection_wait_count": 31835, + "connection_wait_nanos": 9569799, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4663, + "fuse_adapter_attr_misses": 5646, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5291, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27131, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 621888, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4739, + "fuse_flush_ranges": 4739, + "fuse_getattr_count": 10309, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11745, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 466660315, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 41, + "fuse_op_forget_nanos": 1110539, + "fuse_op_getattr_count": 10309, + "fuse_op_getattr_nanos": 69093409, + "fuse_op_lookup_count": 11745, + "fuse_op_lookup_nanos": 127269365, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 92790276, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 30095340, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3082433, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1107270437, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 26971157, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16930, + "fuse_read_lane_wait_nanos": 2499570, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37392, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1939550, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2319664 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15079, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 785728294, + "agentfs_batcher_commit_txns": 4787, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4739, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9398, + "attr_cache_hits": 26181, + "attr_cache_misses": 30842, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 6, + "connection_reuse": 31829, + "connection_wait_count": 31835, + "connection_wait_nanos": 9569799, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2319664 + }, + "fuse": { + "fuse_adapter_attr_hits": 4663, + "fuse_adapter_attr_misses": 5646, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5291, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27131, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 621888, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4739, + "fuse_flush_ranges": 4739, + "fuse_getattr_count": 10309, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11745, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 466660315, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 41, + "fuse_op_forget_nanos": 1110539, + "fuse_op_getattr_count": 10309, + "fuse_op_getattr_nanos": 69093409, + "fuse_op_lookup_count": 11745, + "fuse_op_lookup_nanos": 127269365, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 92790276, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 30095340, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3082433, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1107270437, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 26971157, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16930, + "fuse_read_lane_wait_nanos": 2499570, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 37392, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1939550 + } + }, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 789078874, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26200, + "attr_cache_misses": 30875, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33533, + "connection_wait_count": 33539, + "connection_wait_nanos": 10020125, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6038, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30207, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11690, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12037, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11690, + "fuse_op_getattr_nanos": 70629179, + "fuse_op_lookup_count": 12037, + "fuse_op_lookup_nanos": 132559094, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 94824590, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 33632754, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1113299419, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 28563737, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17925, + "fuse_read_lane_wait_nanos": 2625281, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43238, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1956209, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20812, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26123, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14081728 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 789078874, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26200, + "attr_cache_misses": 30875, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33533, + "connection_wait_count": 33539, + "connection_wait_nanos": 10020125, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14052, + "getattr_count": 20812, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26123, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14081728 + }, + "fuse": { + "fuse_adapter_attr_hits": 6038, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30207, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11690, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12037, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11690, + "fuse_op_getattr_nanos": 70629179, + "fuse_op_lookup_count": 12037, + "fuse_op_lookup_nanos": 132559094, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 94824590, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 33632754, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1113299419, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 28563737, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17925, + "fuse_read_lane_wait_nanos": 2625281, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43238, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1956209 + } + }, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 789078874, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26201, + "attr_cache_misses": 30875, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33541, + "connection_wait_count": 33547, + "connection_wait_nanos": 10024330, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6107, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30283, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11759, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11759, + "fuse_op_getattr_nanos": 70679151, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 132584246, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 94824590, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1113299419, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 28563737, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2626158, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43314, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1956209, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20812, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14081728 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 789078874, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26201, + "attr_cache_misses": 30875, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33541, + "connection_wait_count": 33547, + "connection_wait_nanos": 10024330, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "getattr_count": 20812, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 14081728 + }, + "fuse": { + "fuse_adapter_attr_hits": 6107, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30283, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11759, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11759, + "fuse_op_getattr_nanos": 70679151, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 132584246, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 94824590, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1113299419, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 28563737, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17927, + "fuse_read_lane_wait_nanos": 2626158, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43314, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1956209 + } + }, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26201, + "attr_cache_misses": 30899, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33573, + "connection_wait_count": 33579, + "connection_wait_nanos": 10038144, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6123, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30307, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 11775, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11775, + "fuse_op_getattr_nanos": 70702980, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 132584246, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 95811145, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17935, + "fuse_read_lane_wait_nanos": 2628258, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20828, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14927587 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26201, + "attr_cache_misses": 30899, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33573, + "connection_wait_count": 33579, + "connection_wait_nanos": 10038144, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "getattr_count": 20828, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6811, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14927587 + }, + "fuse": { + "fuse_adapter_attr_hits": 6123, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5292, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30307, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 11775, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12038, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 11775, + "fuse_op_getattr_nanos": 70702980, + "fuse_op_lookup_count": 12038, + "fuse_op_lookup_nanos": 132584246, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 95811145, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17935, + "fuse_read_lane_wait_nanos": 2628258, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43354, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998 + } + }, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26201, + "attr_cache_misses": 30899, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33573, + "connection_wait_count": 33579, + "connection_wait_nanos": 10038144, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7972, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5381, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32245, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13624, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12127, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13624, + "fuse_op_getattr_nanos": 71828730, + "fuse_op_lookup_count": 12127, + "fuse_op_lookup_nanos": 132685455, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 95820142, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17935, + "fuse_read_lane_wait_nanos": 2628258, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45304, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20828, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6900, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14927587 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26201, + "attr_cache_misses": 30899, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33573, + "connection_wait_count": 33579, + "connection_wait_nanos": 10038144, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "getattr_count": 20828, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6900, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14927587 + }, + "fuse": { + "fuse_adapter_attr_hits": 7972, + "fuse_adapter_attr_misses": 5652, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5381, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32245, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13624, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12127, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13624, + "fuse_op_getattr_nanos": 71828730, + "fuse_op_lookup_count": 12127, + "fuse_op_lookup_nanos": 132685455, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 95820142, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 34411161, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 72577224, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17935, + "fuse_read_lane_wait_nanos": 2628258, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45304, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998 + } + }, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26204, + "attr_cache_misses": 30901, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33597, + "connection_wait_count": 33603, + "connection_wait_nanos": 10057617, + "dentry_cache_hits": 26128, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8016, + "fuse_adapter_attr_misses": 5653, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5629, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32562, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13669, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12379, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13669, + "fuse_op_getattr_nanos": 71908427, + "fuse_op_lookup_count": 12379, + "fuse_op_lookup_nanos": 133075470, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 95832914, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34730889, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 73690882, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 2633477, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45657, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7148, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26128, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 15268917 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26204, + "attr_cache_misses": 30901, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33597, + "connection_wait_count": 33603, + "connection_wait_nanos": 10057617, + "dentry_cache_hits": 26128, + "dentry_cache_misses": 14054, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7148, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26128, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 15268917 + }, + "fuse": { + "fuse_adapter_attr_hits": 8016, + "fuse_adapter_attr_misses": 5653, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5629, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32562, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13669, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12379, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13669, + "fuse_op_getattr_nanos": 71908427, + "fuse_op_lookup_count": 12379, + "fuse_op_lookup_nanos": 133075470, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 95832914, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34730889, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 73690882, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 2633477, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45657, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1957998 + } + }, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26204, + "attr_cache_misses": 30901, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33600, + "connection_wait_count": 33606, + "connection_wait_nanos": 10063677, + "dentry_cache_hits": 26128, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8016, + "fuse_adapter_attr_misses": 5653, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5629, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32562, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13669, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12379, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13669, + "fuse_op_getattr_nanos": 71908427, + "fuse_op_lookup_count": 12379, + "fuse_op_lookup_nanos": 133075470, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 95832914, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34730889, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 73690882, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 2633477, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45657, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1959361, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7148, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26128, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 15442194 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790419926, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 16, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1380352, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26204, + "attr_cache_misses": 30901, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 6, + "connection_reuse": 33600, + "connection_wait_count": 33606, + "connection_wait_nanos": 10063677, + "dentry_cache_hits": 26128, + "dentry_cache_misses": 14054, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7148, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26128, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 15442194 + }, + "fuse": { + "fuse_adapter_attr_hits": 8016, + "fuse_adapter_attr_misses": 5653, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5629, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32562, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 693727, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13669, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12379, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 467411983, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 429, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 1161602, + "fuse_op_getattr_count": 13669, + "fuse_op_getattr_nanos": 71908427, + "fuse_op_lookup_count": 12379, + "fuse_op_lookup_nanos": 133075470, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 664, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 95832914, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34730889, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 73690882, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1115706655, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29013824, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 2633477, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45657, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1959361 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 64580, + "stderr_tail": "st_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":790419926,\"agentfs_batcher_commit_txns\":4799,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4753,\"agentfs_batcher_pending_max_bytes\":1380352,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9410,\"attr_cache_hits\":26201,\"attr_cache_misses\":30899,\"chunk_read_chunks\":267,\"chunk_read_queries\":122,\"chunk_write_chunks\":1003,\"connection_create\":6,\"connection_reuse\":33573,\"connection_wait_count\":33579,\"connection_wait_nanos\":10038144,\"dentry_cache_hits\":26124,\"dentry_cache_misses\":14052,\"getattr_count\":20828,\"lookup_base_count\":23,\"lookup_count\":41052,\"lookup_delta_count\":7547,\"lookup_whiteout_count\":0,\"negative_cache_hits\":6900,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46085,\"negative_lookup_count\":14920,\"path_cache_hits\":26124,\"path_cache_misses\":14052,\"path_component_count\":37632,\"path_resolution_count\":8140,\"readdir_count\":1,\"readdir_plus_count\":1398,\"wal_checkpoint_count\":708,\"wal_checkpoint_nanos\":14927587},\"fuse\":{\"fuse_adapter_attr_hits\":7972,\"fuse_adapter_attr_misses\":5652,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6734,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5381,\"fuse_adapter_negative_misses\":6734,\"fuse_callback_count\":32245,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":693727,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4753,\"fuse_flush_ranges\":4753,\"fuse_getattr_count\":13624,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12127,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":467411983,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":429,\"fuse_op_forget_count\":44,\"fuse_op_forget_nanos\":1161602,\"fuse_op_getattr_count\":13624,\"fuse_op_getattr_nanos\":71828730,\"fuse_op_lookup_count\":12127,\"fuse_op_lookup_nanos\":132685455,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":664,\"fuse_op_other_count\":3581,\"fuse_op_other_nanos\":95820142,\"fuse_op_read_count\":239,\"fuse_op_read_nanos\":34411161,\"fuse_op_readdirplus_count\":1407,\"fuse_op_readdirplus_nanos\":72577224,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1115706655,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":29013824,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":239,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17935,\"fuse_read_lane_wait_nanos\":2628258,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1407,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45304,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":1957998}},\"source\":\"phase-checkpoint-6\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":790419926,\"agentfs_batcher_commit_txns\":4799,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4753,\"agentfs_batcher_pending_max_bytes\":1380352,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9410,\"attr_cache_hits\":26204,\"attr_cache_misses\":30901,\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create\":6,\"connection_reuse\":33597,\"connection_wait_count\":33603,\"connection_wait_nanos\":10057617,\"dentry_cache_hits\":26128,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":8016,\"fuse_adapter_attr_misses\":5653,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5629,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32562,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":693727,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4753,\"fuse_flush_ranges\":4753,\"fuse_getattr_count\":13669,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12379,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":467411983,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":429,\"fuse_op_forget_count\":44,\"fuse_op_forget_nanos\":1161602,\"fuse_op_getattr_count\":13669,\"fuse_op_getattr_nanos\":71908427,\"fuse_op_lookup_count\":12379,\"fuse_op_lookup_nanos\":133075470,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":664,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":95832914,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34730889,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":73690882,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1115706655,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":29013824,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17950,\"fuse_read_lane_wait_nanos\":2633477,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45657,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":1957998,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20830,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7148,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26128,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":717,\"wal_checkpoint_nanos\":15268917},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":790419926,\"agentfs_batcher_commit_txns\":4799,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4753,\"agentfs_batcher_pending_max_bytes\":1380352,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9410,\"attr_cache_hits\":26204,\"attr_cache_misses\":30901,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create\":6,\"connection_reuse\":33597,\"connection_wait_count\":33603,\"connection_wait_nanos\":10057617,\"dentry_cache_hits\":26128,\"dentry_cache_misses\":14054,\"getattr_count\":20830,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7148,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26128,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":717,\"wal_checkpoint_nanos\":15268917},\"fuse\":{\"fuse_adapter_attr_hits\":8016,\"fuse_adapter_attr_misses\":5653,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5629,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32562,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":693727,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4753,\"fuse_flush_ranges\":4753,\"fuse_getattr_count\":13669,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12379,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":467411983,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":429,\"fuse_op_forget_count\":44,\"fuse_op_forget_nanos\":1161602,\"fuse_op_getattr_count\":13669,\"fuse_op_getattr_nanos\":71908427,\"fuse_op_lookup_count\":12379,\"fuse_op_lookup_nanos\":133075470,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":664,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":95832914,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34730889,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":73690882,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1115706655,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":29013824,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17950,\"fuse_read_lane_wait_nanos\":2633477,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45657,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":1957998}},\"source\":\"phase-checkpoint-7\"}\n\nSession: git-workload-5199c09fd6244c128bcf18f1942ccf0b\n\nTo resume this session:\n agentfs run --session git-workload-5199c09fd6244c128bcf18f1942ccf0b\n\nTo see what changed:\n agentfs diff git-workload-5199c09fd6244c128bcf18f1942ccf0b\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":790419926,\"agentfs_batcher_commit_txns\":4799,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4753,\"agentfs_batcher_pending_max_bytes\":1380352,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9410,\"attr_cache_hits\":26204,\"attr_cache_misses\":30901,\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create\":6,\"connection_reuse\":33600,\"connection_wait_count\":33606,\"connection_wait_nanos\":10063677,\"dentry_cache_hits\":26128,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":8016,\"fuse_adapter_attr_misses\":5653,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5629,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32562,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":693727,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4753,\"fuse_flush_ranges\":4753,\"fuse_getattr_count\":13669,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12379,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":467411983,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":429,\"fuse_op_forget_count\":44,\"fuse_op_forget_nanos\":1161602,\"fuse_op_getattr_count\":13669,\"fuse_op_getattr_nanos\":71908427,\"fuse_op_lookup_count\":12379,\"fuse_op_lookup_nanos\":133075470,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":664,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":95832914,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34730889,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":73690882,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1115706655,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":29013824,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17950,\"fuse_read_lane_wait_nanos\":2633477,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45657,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10270,\"fuse_write_lane_wait_nanos\":1959361,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20830,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7148,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26128,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":719,\"wal_checkpoint_nanos\":15442194},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":790419926,\"agentfs_batcher_commit_txns\":4799,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":16,\"agentfs_batcher_enqueues\":4753,\"agentfs_batcher_pending_max_bytes\":1380352,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9410,\"attr_cache_hits\":26204,\"attr_cache_misses\":30901,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create\":6,\"connection_reuse\":33600,\"connection_wait_count\":33606,\"connection_wait_nanos\":10063677,\"dentry_cache_hits\":26128,\"dentry_cache_misses\":14054,\"getattr_count\":20830,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7148,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26128,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":719,\"wal_checkpoint_nanos\":15442194},\"fuse\":{\"fuse_adapter_attr_hits\":8016,\"fuse_adapter_attr_misses\":5653,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5629,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32562,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":693727,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4753,\"fuse_flush_ranges\":4753,\"fuse_getattr_count\":13669,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12379,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":467411983,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":429,\"fuse_op_forget_count\":44,\"fuse_op_forget_nanos\":1161602,\"fuse_op_getattr_count\":13669,\"fuse_op_getattr_nanos\":71908427,\"fuse_op_lookup_count\":12379,\"fuse_op_lookup_nanos\":133075470,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":664,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":95832914,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34730889,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":73690882,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1115706655,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":29013824,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17950,\"fuse_read_lane_wait_nanos\":2633477,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45657,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10270,\"fuse_write_lane_wait_nanos\":1959361}},\"source\":\"cli\"}\n", + "stdout_bytes": 18646, + "stdout_tail": "2026-07-04T01:14:16.536441Z INFO agentfs::fuser::session: resolved FUSE dispatch mode: parallel workers=7 queue_capacity=28\n2026-07-04T01:14:16.536695Z INFO agentfs::fuse: advertising FUSE_OVER_IO_URING\n2026-07-04T01:14:16.536731Z INFO agentfs::fuser::uring: starting fuse-over-io_uring queues nr_queues=14 depth=4\n2026-07-04T01:14:17.922551Z WARN agentfs::fuser::request: Request RequestId(30305): Failed to send reply: No such file or directory (os error 2)\n2026-07-04T01:14:18.922565Z WARN agentfs::fuser::request: Request RequestId(53893): Failed to send reply: No such file or directory (os error 2)\n{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.06820097500167321, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work\", \"duration_seconds\": 0.024116513988701627, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work\", \"duration_seconds\": 0.018570251006167382, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work\", \"duration_seconds\": 0.025458272008108906, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.004993759008357301, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work\", \"duration_seconds\": 0.2147588650113903, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work\", \"duration_seconds\": 0.054057981993537396, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/mirror.git\", \"/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/agentfs-base\", \"duration_seconds\": 2.7699222920055036, \"returncode\": 0, \"stderr_bytes\": 2076, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\nUpdating files: 45% (2099/4644)\\nUpdating files: 46% (2137/4644)\\nUpdating files: 47% (2183/4644)\\nUpdating files: 48% (2230/4644)\\nUpdating files: 49% (2276/4644)\\nUpdating files: 50% (2322/4644)\\nUpdating files: 51% (2369/4644)\\nUpdating files: 52% (2415/4644)\\nUpdating files: 53% (2462/4644)\\nUpdating files: 54% (2508/4644)\\nUpdating files: 55% (2555/4644)\\nUpdating files: 56% (2601/4644)\\nUpdating files: 57% (2648/4644)\\nUpdating files: 58% (2694/4644)\\nUpdating files: 59% (2740/4644)\\nUpdating files: 60% (2787/4644)\\nUpdating files: 61% (2833/4644)\\nUpdating files: 62% (2880/4644)\\nUpdating files: 63% (2926/4644)\\nUpdating files: 64% (2973/4644)\\nUpdating files: 65% (3019/4644)\\nUpdating files: 66% (3066/4644)\\nUpdating files: 67% (3112/4644)\\nUpdating files: 68% (3158/4644)\\nUpdating files: 69% (3205/4644)\\nUpdating files: 70% (3251/4644)\\nUpdating files: 71% (3298/4644)\\nUpdating files: 72% (3344/4644)\\nUpdating files: 73% (3391/4644)\\nUpdating files: 74% (3437/4644)\\nUpdating files: 75% (3483/4644)\\nUpdating files: 76% (3530/4644)\\nUpdating files: 77% (3576/4644)\\nUpdating files: 78% (3623/4644)\\nUpdating files: 79% (3669/4644)\\nUpdating files: 80% (3716/4644)\\nUpdating files: 81% (3762/4644)\\nUpdating files: 82% (3809/4644)\\nUpdating files: 83% (3855/4644)\\nUpdating files: 83% (3898/4644)\\nUpdating files: 84% (3901/4644)\\nUpdating files: 85% (3948/4644)\\nUpdating files: 86% (3994/4644)\\nUpdating files: 87% (4041/4644)\\nUpdating files: 88% (4087/4644)\\nUpdating files: 89% (4134/4644)\\nUpdating files: 90% (4180/4644)\\nUpdating files: 91% (4227/4644)\\nUpdating files: 92% (4273/4644)\\nUpdating files: 93% (4319/4644)\\nUpdating files: 94% (4366/4644)\\nUpdating files: 95% (4412/4644)\\nUpdating files: 96% (4459/4644)\\nUpdating files: 97% (4505/4644)\\nUpdating files: 98% (4552/4644)\\nUpdating files: 99% (4598/4644)\\nUpdating files: 100% (4644/4644)\\nUpdating files: 100% (4644/4644), done.\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work\", \"duration_seconds\": 0.036359681995236315, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work\", \"duration_seconds\": 0.14226867599063553, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.056683568996959366, \"clone\": 2.769973303991719, \"diff\": 0.06820097500167321, \"edit\": 0.004993759008357301, \"fsck\": 0.21477365799364634, \"read_search\": 0.0078469020081684, \"status\": 0.17865806899499148}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work\", \"duration_seconds\": 0.004879718006122857, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 4.001881017000414}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.06820097500167321, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work", + "duration_seconds": 0.024116513988701627, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work", + "duration_seconds": 0.018570251006167382, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work", + "duration_seconds": 0.025458272008108906, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.004993759008357301, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work", + "duration_seconds": 0.2147588650113903, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work", + "duration_seconds": 0.054057981993537396, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/mirror.git", + "/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/agentfs-base", + "duration_seconds": 2.7699222920055036, + "returncode": 0, + "stderr_bytes": 2076, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\nUpdating files: 45% (2099/4644)\nUpdating files: 46% (2137/4644)\nUpdating files: 47% (2183/4644)\nUpdating files: 48% (2230/4644)\nUpdating files: 49% (2276/4644)\nUpdating files: 50% (2322/4644)\nUpdating files: 51% (2369/4644)\nUpdating files: 52% (2415/4644)\nUpdating files: 53% (2462/4644)\nUpdating files: 54% (2508/4644)\nUpdating files: 55% (2555/4644)\nUpdating files: 56% (2601/4644)\nUpdating files: 57% (2648/4644)\nUpdating files: 58% (2694/4644)\nUpdating files: 59% (2740/4644)\nUpdating files: 60% (2787/4644)\nUpdating files: 61% (2833/4644)\nUpdating files: 62% (2880/4644)\nUpdating files: 63% (2926/4644)\nUpdating files: 64% (2973/4644)\nUpdating files: 65% (3019/4644)\nUpdating files: 66% (3066/4644)\nUpdating files: 67% (3112/4644)\nUpdating files: 68% (3158/4644)\nUpdating files: 69% (3205/4644)\nUpdating files: 70% (3251/4644)\nUpdating files: 71% (3298/4644)\nUpdating files: 72% (3344/4644)\nUpdating files: 73% (3391/4644)\nUpdating files: 74% (3437/4644)\nUpdating files: 75% (3483/4644)\nUpdating files: 76% (3530/4644)\nUpdating files: 77% (3576/4644)\nUpdating files: 78% (3623/4644)\nUpdating files: 79% (3669/4644)\nUpdating files: 80% (3716/4644)\nUpdating files: 81% (3762/4644)\nUpdating files: 82% (3809/4644)\nUpdating files: 83% (3855/4644)\nUpdating files: 83% (3898/4644)\nUpdating files: 84% (3901/4644)\nUpdating files: 85% (3948/4644)\nUpdating files: 86% (3994/4644)\nUpdating files: 87% (4041/4644)\nUpdating files: 88% (4087/4644)\nUpdating files: 89% (4134/4644)\nUpdating files: 90% (4180/4644)\nUpdating files: 91% (4227/4644)\nUpdating files: 92% (4273/4644)\nUpdating files: 93% (4319/4644)\nUpdating files: 94% (4366/4644)\nUpdating files: 95% (4412/4644)\nUpdating files: 96% (4459/4644)\nUpdating files: 97% (4505/4644)\nUpdating files: 98% (4552/4644)\nUpdating files: 99% (4598/4644)\nUpdating files: 100% (4644/4644)\nUpdating files: 100% (4644/4644), done.\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work", + "duration_seconds": 0.036359681995236315, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work", + "duration_seconds": 0.14226867599063553, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.056683568996959366, + "clone": 2.769973303991719, + "diff": 0.06820097500167321, + "edit": 0.004993759008357301, + "fsck": 0.21477365799364634, + "read_search": 0.0078469020081684, + "status": 0.17865806899499148 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/agentfs-base/work", + "duration_seconds": 0.004879718006122857, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 4.001881017000414 + } + }, + "base_tree": { + "after": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "2ddd357d6bf9e7832f411f450c2781a7ed4cb349ffe6ff1074bf523bf7a0cc7c", + "symlinks": 0 + }, + "before": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "2ddd357d6bf9e7832f411f450c2781a7ed4cb349ffe6ff1074bf523bf7a0cc7c", + "symlinks": 0 + }, + "unchanged": true + }, + "benchmark": "phase7-git-workload", + "command": { + "agentfs_prefix": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-5199c09fd6244c128bcf18f1942ccf0b", + "--no-default-allows", + "--" + ], + "argv": [ + "/home/ain3sh/factory/vfs/scripts/validation/git-workload-benchmark.py", + "--output", + "/tmp/vfs-val/misc-a-bench-rebaseline/run3.json.iterations/iter-03.json", + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "workload_argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ] + }, + "correctness": { + "agentfs_backup_verify": true, + "agentfs_base_unchanged": true, + "agentfs_db_inspectable": true, + "agentfs_integrity_require_portable": true, + "agentfs_no_nonempty_sidecars": true, + "agentfs_portable": true, + "agentfs_returncode_zero": true, + "equivalence": { + "agentfs": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + }, + "checked": true, + "equivalent": true, + "native": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + } + }, + "native_returncode_zero": true, + "passed": true, + "performance_passed": false + }, + "database": { + "after": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-hulvm8vq/home/.agentfs/run/git-workload-5199c09fd6244c128bcf18f1942ccf0b/delta.db" + } + ], + "path": "/tmp/agentfs-git-workload-hulvm8vq/home/.agentfs/run/git-workload-5199c09fd6244c128bcf18f1942ccf0b/delta.db", + "total_bytes": 57147392 + }, + "backup": { + "artifacts": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-hulvm8vq/git-workload-backup.db" + } + ], + "path": "/tmp/agentfs-git-workload-hulvm8vq/git-workload-backup.db", + "total_bytes": 57147392 + }, + "inspect": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "path": "/tmp/agentfs-git-workload-hulvm8vq/git-workload-backup.db", + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "backup", + "/tmp/agentfs-git-workload-hulvm8vq/home/.agentfs/run/git-workload-5199c09fd6244c128bcf18f1942ccf0b/delta.db", + "/tmp/agentfs-git-workload-hulvm8vq/git-workload-backup.db", + "--verify" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq", + "duration_seconds": 2.7751211089926073, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 0, + "fuse_readdirplus_mode": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_workers_configured": 0, + "fuse_writeback_cache_enabled": 0 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "fuse": { + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 7355, + "stderr_tail": "{\"counters\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_mode\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_workers_configured\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0,\"fuse_writeback_cache_enabled\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":0,\"fuse_readdirplus_mode\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_workers_configured\":0,\"fuse_writeback_cache_enabled\":0},\"core\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"fuse\":{\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0}},\"source\":\"cli\"}\n", + "stdout_bytes": 241, + "stdout_tail": "Source: /tmp/agentfs-git-workload-hulvm8vq/home/.agentfs/run/git-workload-5199c09fd6244c128bcf18f1942ccf0b/delta.db\nBackup: /tmp/agentfs-git-workload-hulvm8vq/git-workload-backup.db\nCheckpoint: complete\nCopy: complete\nVerification: complete\n", + "timed_out": false + } + }, + "inspect_after": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "integrity": { + "result": { + "checks": [ + { + "detail": "ok", + "name": "pragma.integrity_check", + "ok": true, + "violating_rows": null + }, + { + "detail": "present", + "name": "schema.table.fs_config", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_symlink", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.kv_store", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.tool_calls", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 0.5", + "name": "config.schema_version", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 65536", + "name": "config.chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 16384", + "name": "config.inline_threshold", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.kind_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_has_no_chunks", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunked_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_size_matches_blob", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.non_regular_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_reference_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunk_length_within_chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 1, expected 1", + "name": "namespace.root_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_is_directory", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_target_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_root_inode_has_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_names_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_directory_nlink_matches_dentries", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.directory_nlink_positive", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.rows_reference_symlink_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.inodes_have_rows", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable: no partial-origin rows", + "name": "overlay.portability_status", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable requirement satisfied", + "name": "overlay.require_portable", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_regular", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_sizes_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_paths_absolute", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_references_partial_origin", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_unique", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_index_in_range", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.whiteout_paths_absolute", + "ok": true, + "violating_rows": 0 + } + ], + "database": "/tmp/agentfs-git-workload-hulvm8vq/home/.agentfs/run/git-workload-5199c09fd6244c128bcf18f1942ccf0b/delta.db", + "ok": true, + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true + }, + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "integrity", + "/tmp/agentfs-git-workload-hulvm8vq/home/.agentfs/run/git-workload-5199c09fd6244c128bcf18f1942ccf0b/delta.db", + "--json", + "--require-portable" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq", + "duration_seconds": 2.6715307739941636, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 0, + "fuse_readdirplus_mode": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_workers_configured": 0, + "fuse_writeback_cache_enabled": 0 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "fuse": { + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 7355, + "stderr_tail": "{\"counters\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_mode\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_workers_configured\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0,\"fuse_writeback_cache_enabled\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":0,\"fuse_readdirplus_mode\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_workers_configured\":0,\"fuse_writeback_cache_enabled\":0},\"core\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"fuse\":{\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0}},\"source\":\"cli\"}\n", + "stdout_bytes": 6395, + "stdout_tail": "{\n \"database\": \"/tmp/agentfs-git-workload-hulvm8vq/home/.agentfs/run/git-workload-5199c09fd6244c128bcf18f1942ccf0b/delta.db\",\n \"ok\": true,\n \"portable\": true,\n \"origin_backed\": false,\n \"partial_origin_rows\": 0,\n \"checks\": [\n {\n \"name\": \"pragma.integrity_check\",\n \"ok\": true,\n \"detail\": \"ok\",\n \"violating_rows\": null\n },\n {\n \"name\": \"schema.table.fs_config\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_inode\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_dentry\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_data\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_symlink\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.kv_store\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.tool_calls\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.schema_version\",\n \"ok\": true,\n \"detail\": \"found 0.5\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.chunk_size\",\n \"ok\": true,\n \"detail\": \"found 65536\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.inline_threshold\",\n \"ok\": true,\n \"detail\": \"found 16384\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.kind_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_has_no_chunks\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunked_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_size_matches_blob\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.non_regular_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_reference_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunk_length_within_chunk_size\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.root_inode\",\n \"ok\": true,\n \"detail\": \"found 1, expected 1\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_is_directory\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_target_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_root_inode_has_dentry\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_names_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_directory_nlink_matches_dentries\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.directory_nlink_positive\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.rows_reference_symlink_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.inodes_have_rows\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.portability_status\",\n \"ok\": true,\n \"detail\": \"portable: no partial-origin rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.require_portable\",\n \"ok\": true,\n \"detail\": \"portable requirement satisfied\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_regular\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_sizes_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_references_partial_origin\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_unique\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_index_in_range\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.whiteout_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n }\n ]\n}\n", + "timed_out": false + } + }, + "nonempty_sidecars": false + }, + "environment": { + "AGENTFS_BIN": "/home/ain3sh/factory/vfs/target/release/agentfs", + "AGENTFS_PROFILE": "1" + }, + "git_commit": "2b811f03493e861e5a7786592d25ac73b39e1aba", + "kept_temp": false, + "native": { + "run": { + "argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/native", + "duration_seconds": 1.462491037993459, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 15984, + "stdout_tail": "{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.39594387999386527, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/native/work\", \"duration_seconds\": 0.11724154200055636, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/native/work\", \"duration_seconds\": 0.1309305199974915, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/native/work\", \"duration_seconds\": 0.1476996660057921, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.0009980429895222187, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/native/work\", \"duration_seconds\": 0.19250968800042756, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/native/work\", \"duration_seconds\": 0.20070338300138246, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-hulvm8vq/native/mirror.git\", \"/tmp/agentfs-git-workload-hulvm8vq/native/work\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/native\", \"duration_seconds\": 0.33443757600616664, \"returncode\": 0, \"stderr_bytes\": 149, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-hulvm8vq/native/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/native/work\", \"duration_seconds\": 0.15876751000178047, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/native/work\", \"duration_seconds\": 0.10637031801161356, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.20305690400709864, \"clone\": 0.3344773830031045, \"diff\": 0.39594387999386527, \"edit\": 0.0009980429895222187, \"fsck\": 0.19252638499892782, \"read_search\": 0.010034471997641958, \"status\": 0.26516752400493715}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-hulvm8vq/native/work\", \"duration_seconds\": 0.005001552010071464, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 1.4025006750016473}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.39594387999386527, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/native/work", + "duration_seconds": 0.11724154200055636, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/native/work", + "duration_seconds": 0.1309305199974915, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/native/work", + "duration_seconds": 0.1476996660057921, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.0009980429895222187, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/native/work", + "duration_seconds": 0.19250968800042756, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/native/work", + "duration_seconds": 0.20070338300138246, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-hulvm8vq/native/mirror.git", + "/tmp/agentfs-git-workload-hulvm8vq/native/work" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/native", + "duration_seconds": 0.33443757600616664, + "returncode": 0, + "stderr_bytes": 149, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-hulvm8vq/native/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/native/work", + "duration_seconds": 0.15876751000178047, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/native/work", + "duration_seconds": 0.10637031801161356, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.20305690400709864, + "clone": 0.3344773830031045, + "diff": 0.39594387999386527, + "edit": 0.0009980429895222187, + "fsck": 0.19252638499892782, + "read_search": 0.010034471997641958, + "status": 0.26516752400493715 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-hulvm8vq/native/work", + "duration_seconds": 0.005001552010071464, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 1.4025006750016473 + } + }, + "parameters": { + "edit_files": 8, + "fixture_dirs": 8, + "fixture_file_size_bytes": 1024, + "fixture_files": 96, + "read_bytes": 4096, + "read_files": 64, + "search_token": "AGENTFS_TOKEN", + "skip_fsck": false, + "timeout_seconds": 180.0 + }, + "schema_version": 1, + "source": { + "kind": "source", + "mirror_head": "7d47056ea42636271ac020b86347fbbef49490aa", + "path": "/home/ain3sh/factory/vfs/.agents/benchmarks/fixtures/codex" + }, + "summary": { + "agentfs_base_unchanged": true, + "agentfs_seconds": 4.001881017000414, + "all_equivalent": true, + "correctness_passed": true, + "native_seconds": 1.4025006750016473, + "passed": true, + "performance_passed": false, + "phase_ratios": { + "checkout": { + "agentfs_seconds": 0.056683568996959366, + "native_seconds": 0.20305690400709864, + "ratio": 0.27915115358489745 + }, + "clone": { + "agentfs_seconds": 2.769973303991719, + "native_seconds": 0.3344773830031045, + "ratio": 8.281496581686689 + }, + "diff": { + "agentfs_seconds": 0.06820097500167321, + "native_seconds": 0.39594387999386527, + "ratio": 0.172249095004903 + }, + "edit": { + "agentfs_seconds": 0.004993759008357301, + "native_seconds": 0.0009980429895222187, + "ratio": 5.0035510101102005 + }, + "fsck": { + "agentfs_seconds": 0.21477365799364634, + "native_seconds": 0.19252638499892782, + "ratio": 1.115554410866035 + }, + "read_search": { + "agentfs_seconds": 0.0078469020081684, + "native_seconds": 0.010034471997641958, + "ratio": 0.7819945095279927 + }, + "status": { + "agentfs_seconds": 0.17865806899499148, + "native_seconds": 0.26516752400493715, + "ratio": 0.6737554670973398 + } + }, + "ratio": 2.8533897261730115, + "threshold_failures": [ + { + "agentfs_seconds": 2.769973303991719, + "native_seconds": 0.3344773830031045, + "phase": "clone", + "ratio": 8.281496581686689 + }, + { + "agentfs_seconds": 0.004993759008357301, + "native_seconds": 0.0009980429895222187, + "phase": "edit", + "ratio": 5.0035510101102005 + } + ] + }, + "temp_dir": "/tmp/agentfs-git-workload-hulvm8vq" +} diff --git a/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-04.json b/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-04.json new file mode 100644 index 00000000..fa24b06c --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/iter-04.json @@ -0,0 +1,8680 @@ +{ + "agentfs": { + "bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "db_path": "/tmp/agentfs-git-workload-7wytce9p/home/.agentfs/run/git-workload-fa418f52ee1a41beabeb538335d5466e/delta.db", + "per_phase_counters": { + "checkpoint_count": 7, + "label_count": 7, + "labels_aligned": true, + "phases": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 33, + "agentfs_batcher_commit_latency_ns_total": 849429017, + "agentfs_batcher_commit_txns": 4784, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9387, + "attr_cache_hits": 25922, + "attr_cache_misses": 29051, + "base_fast_inode_invalidations": 15027, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create": 2, + "connection_reuse": 31210, + "connection_wait_count": 31212, + "connection_wait_nanos": 10156344, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 95, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 7, + "fuse_adapter_entry_misses": 6410, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5283, + "fuse_adapter_negative_misses": 6410, + "fuse_callback_count": 21785, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 38, + "fuse_dispatch_wait_count": 38, + "fuse_dispatch_wait_nanos": 377825, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52561419, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5029, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11700, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 493672925, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 549158, + "fuse_op_getattr_count": 5029, + "fuse_op_getattr_nanos": 53374165, + "fuse_op_lookup_count": 11700, + "fuse_op_lookup_nanos": 131275099, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 98313740, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27550934, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1954420, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1180629003, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 27691889, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16151, + "fuse_read_lane_wait_nanos": 2491830, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32009, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1469827, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6776, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45064, + "negative_lookup_count": 14254, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32899, + "path_resolution_count": 7117, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1555852 + }, + "phase": "clone", + "seq": 1 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 2, + "agentfs_batcher_commit_latency_ns_total": 3491682, + "agentfs_batcher_commit_txns": 10, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 5, + "agentfs_batcher_drains_timer": 2, + "agentfs_batcher_enqueues": 9, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 14, + "attr_cache_hits": 212, + "attr_cache_misses": 1746, + "base_fast_inode_invalidations": 51, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 13, + "connection_create": 6, + "connection_reuse": 625, + "connection_wait_count": 631, + "connection_wait_nanos": 238940, + "dentry_cache_hits": 73, + "dentry_cache_misses": 56, + "fuse_adapter_attr_hits": 4591, + "fuse_adapter_attr_misses": 666, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 35, + "fuse_adapter_inval_entry_notifications": 10, + "fuse_adapter_inval_inode_notifications": 28, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 13, + "fuse_adapter_negative_misses": 35, + "fuse_callback_count": 5328, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 4, + "fuse_dispatch_parallel_tasks": 7, + "fuse_dispatch_wait_count": 7, + "fuse_dispatch_wait_nanos": 75286, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 672857, + "fuse_flush_count": 9, + "fuse_flush_ranges": 9, + "fuse_getattr_count": 5257, + "fuse_ino_file_resolutions": 2, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 50, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 9, + "fuse_op_create_nanos": 1004940, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 7, + "fuse_op_forget_nanos": 68125, + "fuse_op_getattr_count": 5257, + "fuse_op_getattr_nanos": 17138258, + "fuse_op_lookup_count": 50, + "fuse_op_lookup_nanos": 613608, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 15, + "fuse_op_other_nanos": 2888818, + "fuse_op_read_count": 8, + "fuse_op_read_nanos": 2494991, + "fuse_op_readdirplus_count": 4, + "fuse_op_readdirplus_nanos": 1183023, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 14, + "fuse_op_setattr_nanos": 3548961, + "fuse_op_write_count": 9, + "fuse_op_write_nanos": 143300, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 8, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 730, + "fuse_read_lane_wait_nanos": 107580, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 4, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5366, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 591393, + "fuse_write_count": 9, + "fuse_write_lane_wait_count": 42, + "fuse_write_lane_wait_nanos": 6961, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 1360, + "lookup_base_count": 0, + "lookup_count": 164, + "lookup_delta_count": 60, + "lookup_whiteout_count": 0, + "negative_cache_hits": 34, + "negative_cache_invalidations": 26, + "negative_cache_misses": 148, + "negative_lookup_count": 86, + "path_cache_hits": 73, + "path_cache_misses": 56, + "path_component_count": 124, + "path_resolution_count": 45, + "readdir_count": 0, + "readdir_plus_count": 4, + "wal_checkpoint_count": 2, + "wal_checkpoint_nanos": 788785 + }, + "phase": "checkout", + "seq": 2 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 4, + "agentfs_batcher_commit_latency_ns_total": 4021263, + "agentfs_batcher_commit_txns": 4, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 2, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 6, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 4, + "attr_cache_hits": 20, + "attr_cache_misses": 34, + "base_fast_inode_invalidations": 24, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 20, + "chunk_read_queries": 12, + "chunk_write_chunks": 20, + "connection_create": 0, + "connection_reuse": 1704, + "connection_wait_count": 1704, + "connection_wait_nanos": 427136, + "dentry_cache_hits": 19, + "dentry_cache_misses": 570, + "fuse_adapter_attr_hits": 1364, + "fuse_adapter_attr_misses": 7, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 289, + "fuse_adapter_inval_entry_notifications": 4, + "fuse_adapter_inval_inode_notifications": 8, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 1, + "fuse_adapter_negative_misses": 289, + "fuse_callback_count": 3066, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 4, + "fuse_dispatch_wait_count": 4, + "fuse_dispatch_wait_nanos": 132373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 1181458, + "fuse_flush_count": 6, + "fuse_flush_ranges": 6, + "fuse_getattr_count": 1371, + "fuse_ino_file_resolutions": 2, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 292, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 2, + "fuse_op_create_nanos": 751027, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 4, + "fuse_op_forget_nanos": 64218, + "fuse_op_getattr_count": 1371, + "fuse_op_getattr_nanos": 1609717, + "fuse_op_lookup_count": 292, + "fuse_op_lookup_nanos": 5070461, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 2762, + "fuse_op_other_nanos": 2744481, + "fuse_op_read_count": 12, + "fuse_op_read_nanos": 1872704, + "fuse_op_readdirplus_count": 1381, + "fuse_op_readdirplus_nanos": 62592380, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 6, + "fuse_op_setattr_nanos": 7333919, + "fuse_op_write_count": 10, + "fuse_op_write_nanos": 1461216, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 12, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 996, + "fuse_read_lane_wait_nanos": 115497, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1381, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5836, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 1181458, + "fuse_write_count": 10, + "fuse_write_lane_wait_count": 14, + "fuse_write_lane_wait_nanos": 24748, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 26, + "lookup_base_count": 0, + "lookup_count": 598, + "lookup_delta_count": 295, + "lookup_whiteout_count": 0, + "negative_cache_hits": 5, + "negative_cache_invalidations": 4, + "negative_cache_misses": 873, + "negative_lookup_count": 578, + "path_cache_hits": 19, + "path_cache_misses": 570, + "path_component_count": 4606, + "path_resolution_count": 977, + "readdir_count": 0, + "readdir_plus_count": 1376, + "wal_checkpoint_count": 688, + "wal_checkpoint_nanos": 10971470 + }, + "phase": "status", + "seq": 3 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 1, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 8, + "connection_wait_count": 8, + "connection_wait_nanos": 12303, + "dentry_cache_hits": 1, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 69, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 1, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 1, + "fuse_callback_count": 76, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 69, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 1, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 69, + "fuse_op_getattr_nanos": 71956, + "fuse_op_lookup_count": 1, + "fuse_op_lookup_nanos": 28778, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 6, + "fuse_op_read_nanos": 1961401, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 6, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 2, + "fuse_read_lane_wait_nanos": 917, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 76, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 2, + "lookup_delta_count": 1, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 2, + "negative_lookup_count": 0, + "path_cache_hits": 1, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "read_search", + "seq": 4 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 1515753, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 8, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 8, + "attr_cache_hits": 0, + "attr_cache_misses": 24, + "base_fast_inode_invalidations": 16, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 32, + "connection_wait_count": 32, + "connection_wait_nanos": 17900, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 16, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 24, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 6398, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 16, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 16, + "fuse_op_getattr_nanos": 31649, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 8, + "fuse_op_other_nanos": 874969, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 8, + "fuse_op_setattr_nanos": 2773164, + "fuse_op_write_count": 8, + "fuse_op_write_nanos": 535669, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 8, + "fuse_read_lane_wait_nanos": 2919, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 40, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 6398, + "fuse_write_count": 8, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 1590, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 16, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 8, + "wal_checkpoint_nanos": 720811 + }, + "phase": "edit", + "seq": 5 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 1861, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 46, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 1907, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 1861, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 46, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 1861, + "fuse_op_getattr_nanos": 1200215, + "fuse_op_lookup_count": 46, + "fuse_op_lookup_nanos": 66868, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 12, + "fuse_op_other_nanos": 14232, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 1919, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 46, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "diff", + "seq": 6 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 3, + "attr_cache_misses": 2, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 1, + "chunk_read_queries": 1, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 24, + "connection_wait_count": 24, + "connection_wait_nanos": 27057, + "dentry_cache_hits": 4, + "dentry_cache_misses": 2, + "fuse_adapter_attr_hits": 44, + "fuse_adapter_attr_misses": 1, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 4, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 244, + "fuse_adapter_negative_misses": 4, + "fuse_callback_count": 313, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 45, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 248, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 45, + "fuse_op_getattr_nanos": 109997, + "fuse_op_lookup_count": 248, + "fuse_op_lookup_nanos": 430358, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 36, + "fuse_op_other_nanos": 14864, + "fuse_op_read_count": 2, + "fuse_op_read_nanos": 390727, + "fuse_op_readdirplus_count": 18, + "fuse_op_readdirplus_nanos": 1553564, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 2, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 15, + "fuse_read_lane_wait_nanos": 6557, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 18, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 349, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 2, + "lookup_base_count": 0, + "lookup_count": 8, + "lookup_delta_count": 4, + "lookup_whiteout_count": 0, + "negative_cache_hits": 244, + "negative_cache_invalidations": 0, + "negative_cache_misses": 9, + "negative_lookup_count": 2, + "path_cache_hits": 4, + "path_cache_misses": 2, + "path_component_count": 42, + "path_resolution_count": 10, + "readdir_count": 0, + "readdir_plus_count": 18, + "wal_checkpoint_count": 9, + "wal_checkpoint_nanos": 506274 + }, + "phase": "fsck", + "seq": 7 + } + ] + }, + "profile_counters": { + "last_by_source": { + "cli": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26158, + "attr_cache_misses": 30857, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33606, + "connection_wait_count": 33614, + "connection_wait_nanos": 10887566, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8040, + "fuse_adapter_attr_misses": 5608, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6739, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5587, + "fuse_adapter_negative_misses": 6739, + "fuse_callback_count": 32499, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13648, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12337, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13648, + "fuse_op_getattr_nanos": 73535957, + "fuse_op_lookup_count": 12337, + "fuse_op_lookup_nanos": 137485172, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 104851104, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34270757, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 67283387, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2725300, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45595, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1504628, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20738, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7105, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46096, + "negative_lookup_count": 14920, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 14679037 + }, + "phase-checkpoint-1": { + "agentfs_batcher_coalesced_ranges": 33, + "agentfs_batcher_commit_latency_ns_total": 849429017, + "agentfs_batcher_commit_txns": 4784, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9387, + "attr_cache_hits": 25922, + "attr_cache_misses": 29051, + "base_fast_inode_invalidations": 15027, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create": 2, + "connection_reuse": 31210, + "connection_wait_count": 31212, + "connection_wait_nanos": 10156344, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 95, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 7, + "fuse_adapter_entry_misses": 6410, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5283, + "fuse_adapter_negative_misses": 6410, + "fuse_callback_count": 21785, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 38, + "fuse_dispatch_wait_count": 38, + "fuse_dispatch_wait_nanos": 377825, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52561419, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5029, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11700, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 493672925, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 549158, + "fuse_op_getattr_count": 5029, + "fuse_op_getattr_nanos": 53374165, + "fuse_op_lookup_count": 11700, + "fuse_op_lookup_nanos": 131275099, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 98313740, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27550934, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1954420, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1180629003, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 27691889, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16151, + "fuse_read_lane_wait_nanos": 2491830, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32009, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1469827, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6776, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45064, + "negative_lookup_count": 14254, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32899, + "path_resolution_count": 7117, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1555852 + }, + "phase-checkpoint-2": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 852920699, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4739, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9401, + "attr_cache_hits": 26134, + "attr_cache_misses": 30797, + "base_fast_inode_invalidations": 15078, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 8, + "connection_reuse": 31835, + "connection_wait_count": 31843, + "connection_wait_nanos": 10395284, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4686, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 9, + "fuse_adapter_entry_misses": 6445, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5296, + "fuse_adapter_negative_misses": 6445, + "fuse_callback_count": 27113, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 45, + "fuse_dispatch_wait_count": 45, + "fuse_dispatch_wait_nanos": 453111, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4739, + "fuse_flush_ranges": 4739, + "fuse_getattr_count": 10286, + "fuse_ino_file_resolutions": 22, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11750, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 494677865, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 617283, + "fuse_op_getattr_count": 10286, + "fuse_op_getattr_nanos": 70512423, + "fuse_op_lookup_count": 11750, + "fuse_op_lookup_nanos": 131888707, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 101202558, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 30045925, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3137443, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1184177964, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 27835189, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16881, + "fuse_read_lane_wait_nanos": 2599410, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37375, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1476788, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20694, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45212, + "negative_lookup_count": 14340, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33023, + "path_resolution_count": 7162, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2344637 + }, + "phase-checkpoint-3": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 856941962, + "agentfs_batcher_commit_txns": 4798, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9405, + "attr_cache_hits": 26154, + "attr_cache_misses": 30831, + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33539, + "connection_wait_count": 33547, + "connection_wait_nanos": 10822420, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6050, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30179, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11657, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12042, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11657, + "fuse_op_getattr_nanos": 72122140, + "fuse_op_lookup_count": 12042, + "fuse_op_lookup_nanos": 136959168, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 103947039, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 31918629, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1191511883, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 29296405, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17877, + "fuse_read_lane_wait_nanos": 2714907, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43211, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1501536, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20720, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13316107 + }, + "phase-checkpoint-4": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 856941962, + "agentfs_batcher_commit_txns": 4798, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9405, + "attr_cache_hits": 26155, + "attr_cache_misses": 30831, + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33547, + "connection_wait_count": 33555, + "connection_wait_nanos": 10834723, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6119, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 30255, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11726, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11726, + "fuse_op_getattr_nanos": 72194096, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 136987946, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 103947039, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1191511883, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 29296405, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17879, + "fuse_read_lane_wait_nanos": 2715824, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43287, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1501536, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20720, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13316107 + }, + "phase-checkpoint-5": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26155, + "attr_cache_misses": 30855, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33579, + "connection_wait_count": 33587, + "connection_wait_nanos": 10852623, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6135, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 30279, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 11742, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11742, + "fuse_op_getattr_nanos": 72225745, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 136987946, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 104822008, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17887, + "fuse_read_lane_wait_nanos": 2718743, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43327, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20736, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14036918 + }, + "phase-checkpoint-6": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26155, + "attr_cache_misses": 30855, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33579, + "connection_wait_count": 33587, + "connection_wait_nanos": 10852623, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7996, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5343, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 32186, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13603, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12089, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13603, + "fuse_op_getattr_nanos": 73425960, + "fuse_op_lookup_count": 12089, + "fuse_op_lookup_nanos": 137054814, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 104836240, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17887, + "fuse_read_lane_wait_nanos": 2718743, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45246, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20736, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6861, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14036918 + }, + "phase-checkpoint-7": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26158, + "attr_cache_misses": 30857, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33603, + "connection_wait_count": 33611, + "connection_wait_nanos": 10879680, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8040, + "fuse_adapter_attr_misses": 5608, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6739, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5587, + "fuse_adapter_negative_misses": 6739, + "fuse_callback_count": 32499, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13648, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12337, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13648, + "fuse_op_getattr_nanos": 73535957, + "fuse_op_lookup_count": 12337, + "fuse_op_lookup_nanos": 137485172, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 104851104, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34270757, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 67283387, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2725300, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45595, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20738, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7105, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46096, + "negative_lookup_count": 14920, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 14543192 + } + }, + "max_counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26158, + "attr_cache_misses": 30857, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33606, + "connection_wait_count": 33614, + "connection_wait_nanos": 10887566, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8040, + "fuse_adapter_attr_misses": 5608, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6739, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5587, + "fuse_adapter_negative_misses": 6739, + "fuse_callback_count": 32499, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13648, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12337, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13648, + "fuse_op_getattr_nanos": 73535957, + "fuse_op_lookup_count": 12337, + "fuse_op_lookup_nanos": 137485172, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 104851104, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34270757, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 67283387, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2725300, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45595, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1504628, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20738, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7105, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46096, + "negative_lookup_count": 14920, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 14679037 + }, + "summary_count": 8 + }, + "profile_enabled": true, + "profile_summary_count": 8, + "session": "git-workload-fa418f52ee1a41beabeb538335d5466e" + }, + "agentfs_overlay": { + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 33, + "agentfs_batcher_commit_latency_ns_total": 849429017, + "agentfs_batcher_commit_txns": 4784, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9387, + "attr_cache_hits": 25922, + "attr_cache_misses": 29051, + "base_fast_inode_invalidations": 15027, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create": 2, + "connection_reuse": 31210, + "connection_wait_count": 31212, + "connection_wait_nanos": 10156344, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 95, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 7, + "fuse_adapter_entry_misses": 6410, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5283, + "fuse_adapter_negative_misses": 6410, + "fuse_callback_count": 21785, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 38, + "fuse_dispatch_wait_count": 38, + "fuse_dispatch_wait_nanos": 377825, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52561419, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5029, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11700, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 493672925, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 549158, + "fuse_op_getattr_count": 5029, + "fuse_op_getattr_nanos": 53374165, + "fuse_op_lookup_count": 11700, + "fuse_op_lookup_nanos": 131275099, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 98313740, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27550934, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1954420, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1180629003, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 27691889, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16151, + "fuse_read_lane_wait_nanos": 2491830, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32009, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1469827, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6776, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45064, + "negative_lookup_count": 14254, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32899, + "path_resolution_count": 7117, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1555852 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15027, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 33, + "agentfs_batcher_commit_latency_ns_total": 849429017, + "agentfs_batcher_commit_txns": 4784, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9387, + "attr_cache_hits": 25922, + "attr_cache_misses": 29051, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create": 2, + "connection_reuse": 31210, + "connection_wait_count": 31212, + "connection_wait_nanos": 10156344, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6776, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45064, + "negative_lookup_count": 14254, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32899, + "path_resolution_count": 7117, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1555852 + }, + "fuse": { + "fuse_adapter_attr_hits": 95, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 7, + "fuse_adapter_entry_misses": 6410, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5283, + "fuse_adapter_negative_misses": 6410, + "fuse_callback_count": 21785, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 38, + "fuse_dispatch_wait_count": 38, + "fuse_dispatch_wait_nanos": 377825, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52561419, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5029, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11700, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 493672925, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 549158, + "fuse_op_getattr_count": 5029, + "fuse_op_getattr_nanos": 53374165, + "fuse_op_lookup_count": 11700, + "fuse_op_lookup_nanos": 131275099, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 98313740, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27550934, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1954420, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1180629003, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 27691889, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16151, + "fuse_read_lane_wait_nanos": 2491830, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 32009, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1469827 + } + }, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 852920699, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4739, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9401, + "attr_cache_hits": 26134, + "attr_cache_misses": 30797, + "base_fast_inode_invalidations": 15078, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 8, + "connection_reuse": 31835, + "connection_wait_count": 31843, + "connection_wait_nanos": 10395284, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4686, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 9, + "fuse_adapter_entry_misses": 6445, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5296, + "fuse_adapter_negative_misses": 6445, + "fuse_callback_count": 27113, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 45, + "fuse_dispatch_wait_count": 45, + "fuse_dispatch_wait_nanos": 453111, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4739, + "fuse_flush_ranges": 4739, + "fuse_getattr_count": 10286, + "fuse_ino_file_resolutions": 22, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11750, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 494677865, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 617283, + "fuse_op_getattr_count": 10286, + "fuse_op_getattr_nanos": 70512423, + "fuse_op_lookup_count": 11750, + "fuse_op_lookup_nanos": 131888707, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 101202558, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 30045925, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3137443, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1184177964, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 27835189, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16881, + "fuse_read_lane_wait_nanos": 2599410, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37375, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1476788, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20694, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45212, + "negative_lookup_count": 14340, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33023, + "path_resolution_count": 7162, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2344637 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15078, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 852920699, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4739, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9401, + "attr_cache_hits": 26134, + "attr_cache_misses": 30797, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 8, + "connection_reuse": 31835, + "connection_wait_count": 31843, + "connection_wait_nanos": 10395284, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "getattr_count": 20694, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45212, + "negative_lookup_count": 14340, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33023, + "path_resolution_count": 7162, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2344637 + }, + "fuse": { + "fuse_adapter_attr_hits": 4686, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 9, + "fuse_adapter_entry_misses": 6445, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5296, + "fuse_adapter_negative_misses": 6445, + "fuse_callback_count": 27113, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 45, + "fuse_dispatch_wait_count": 45, + "fuse_dispatch_wait_nanos": 453111, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4739, + "fuse_flush_ranges": 4739, + "fuse_getattr_count": 10286, + "fuse_ino_file_resolutions": 22, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11750, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 494677865, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 617283, + "fuse_op_getattr_count": 10286, + "fuse_op_getattr_nanos": 70512423, + "fuse_op_lookup_count": 11750, + "fuse_op_lookup_nanos": 131888707, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 101202558, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 30045925, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3137443, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1184177964, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 27835189, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16881, + "fuse_read_lane_wait_nanos": 2599410, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 37375, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1476788 + } + }, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 856941962, + "agentfs_batcher_commit_txns": 4798, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9405, + "attr_cache_hits": 26154, + "attr_cache_misses": 30831, + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33539, + "connection_wait_count": 33547, + "connection_wait_nanos": 10822420, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6050, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30179, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11657, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12042, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11657, + "fuse_op_getattr_nanos": 72122140, + "fuse_op_lookup_count": 12042, + "fuse_op_lookup_nanos": 136959168, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 103947039, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 31918629, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1191511883, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 29296405, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17877, + "fuse_read_lane_wait_nanos": 2714907, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43211, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1501536, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20720, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13316107 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 856941962, + "agentfs_batcher_commit_txns": 4798, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9405, + "attr_cache_hits": 26154, + "attr_cache_misses": 30831, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33539, + "connection_wait_count": 33547, + "connection_wait_nanos": 10822420, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20720, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13316107 + }, + "fuse": { + "fuse_adapter_attr_hits": 6050, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30179, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11657, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12042, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11657, + "fuse_op_getattr_nanos": 72122140, + "fuse_op_lookup_count": 12042, + "fuse_op_lookup_nanos": 136959168, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 103947039, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 31918629, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1191511883, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 29296405, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17877, + "fuse_read_lane_wait_nanos": 2714907, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43211, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1501536 + } + }, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 856941962, + "agentfs_batcher_commit_txns": 4798, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9405, + "attr_cache_hits": 26155, + "attr_cache_misses": 30831, + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33547, + "connection_wait_count": 33555, + "connection_wait_nanos": 10834723, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6119, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 30255, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11726, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11726, + "fuse_op_getattr_nanos": 72194096, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 136987946, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 103947039, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1191511883, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 29296405, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17879, + "fuse_read_lane_wait_nanos": 2715824, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43287, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1501536, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20720, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13316107 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 856941962, + "agentfs_batcher_commit_txns": 4798, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9405, + "attr_cache_hits": 26155, + "attr_cache_misses": 30831, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33547, + "connection_wait_count": 33555, + "connection_wait_nanos": 10834723, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "getattr_count": 20720, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13316107 + }, + "fuse": { + "fuse_adapter_attr_hits": 6119, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 30255, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11726, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11726, + "fuse_op_getattr_nanos": 72194096, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 136987946, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 103947039, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1191511883, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 29296405, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17879, + "fuse_read_lane_wait_nanos": 2715824, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43287, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1501536 + } + }, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26155, + "attr_cache_misses": 30855, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33579, + "connection_wait_count": 33587, + "connection_wait_nanos": 10852623, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6135, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 30279, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 11742, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11742, + "fuse_op_getattr_nanos": 72225745, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 136987946, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 104822008, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17887, + "fuse_read_lane_wait_nanos": 2718743, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43327, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20736, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14036918 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26155, + "attr_cache_misses": 30855, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33579, + "connection_wait_count": 33587, + "connection_wait_nanos": 10852623, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "getattr_count": 20736, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14036918 + }, + "fuse": { + "fuse_adapter_attr_hits": 6135, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 30279, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 11742, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11742, + "fuse_op_getattr_nanos": 72225745, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 136987946, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 104822008, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17887, + "fuse_read_lane_wait_nanos": 2718743, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43327, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126 + } + }, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26155, + "attr_cache_misses": 30855, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33579, + "connection_wait_count": 33587, + "connection_wait_nanos": 10852623, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7996, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5343, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 32186, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13603, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12089, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13603, + "fuse_op_getattr_nanos": 73425960, + "fuse_op_lookup_count": 12089, + "fuse_op_lookup_nanos": 137054814, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 104836240, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17887, + "fuse_read_lane_wait_nanos": 2718743, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45246, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20736, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6861, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14036918 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26155, + "attr_cache_misses": 30855, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33579, + "connection_wait_count": 33587, + "connection_wait_nanos": 10852623, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "getattr_count": 20736, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6861, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14036918 + }, + "fuse": { + "fuse_adapter_attr_hits": 7996, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5343, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 32186, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13603, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12089, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13603, + "fuse_op_getattr_nanos": 73425960, + "fuse_op_lookup_count": 12089, + "fuse_op_lookup_nanos": 137054814, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 104836240, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17887, + "fuse_read_lane_wait_nanos": 2718743, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45246, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126 + } + }, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26158, + "attr_cache_misses": 30857, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33603, + "connection_wait_count": 33611, + "connection_wait_nanos": 10879680, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8040, + "fuse_adapter_attr_misses": 5608, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6739, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5587, + "fuse_adapter_negative_misses": 6739, + "fuse_callback_count": 32499, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13648, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12337, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13648, + "fuse_op_getattr_nanos": 73535957, + "fuse_op_lookup_count": 12337, + "fuse_op_lookup_nanos": 137485172, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 104851104, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34270757, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 67283387, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2725300, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45595, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20738, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7105, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46096, + "negative_lookup_count": 14920, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 14543192 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26158, + "attr_cache_misses": 30857, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33603, + "connection_wait_count": 33611, + "connection_wait_nanos": 10879680, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "getattr_count": 20738, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7105, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46096, + "negative_lookup_count": 14920, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 14543192 + }, + "fuse": { + "fuse_adapter_attr_hits": 8040, + "fuse_adapter_attr_misses": 5608, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6739, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5587, + "fuse_adapter_negative_misses": 6739, + "fuse_callback_count": 32499, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13648, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12337, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13648, + "fuse_op_getattr_nanos": 73535957, + "fuse_op_lookup_count": 12337, + "fuse_op_lookup_nanos": 137485172, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 104851104, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34270757, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 67283387, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2725300, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45595, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126 + } + }, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26158, + "attr_cache_misses": 30857, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33606, + "connection_wait_count": 33614, + "connection_wait_nanos": 10887566, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8040, + "fuse_adapter_attr_misses": 5608, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6739, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5587, + "fuse_adapter_negative_misses": 6739, + "fuse_callback_count": 32499, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13648, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12337, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13648, + "fuse_op_getattr_nanos": 73535957, + "fuse_op_lookup_count": 12337, + "fuse_op_lookup_nanos": 137485172, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 104851104, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34270757, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 67283387, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2725300, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45595, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1504628, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20738, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7105, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46096, + "negative_lookup_count": 14920, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 14679037 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26158, + "attr_cache_misses": 30857, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33606, + "connection_wait_count": 33614, + "connection_wait_nanos": 10887566, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "getattr_count": 20738, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7105, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46096, + "negative_lookup_count": 14920, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 14679037 + }, + "fuse": { + "fuse_adapter_attr_hits": 8040, + "fuse_adapter_attr_misses": 5608, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6739, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5587, + "fuse_adapter_negative_misses": 6739, + "fuse_callback_count": 32499, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13648, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12337, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13648, + "fuse_op_getattr_nanos": 73535957, + "fuse_op_lookup_count": 12337, + "fuse_op_lookup_nanos": 137485172, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 104851104, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34270757, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 67283387, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2725300, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45595, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1504628 + } + }, + "source": "cli" + } + ], + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-fa418f52ee1a41beabeb538335d5466e", + "--no-default-allows", + "--", + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/agentfs-base", + "duration_seconds": 4.218257960994379, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 33, + "agentfs_batcher_commit_latency_ns_total": 849429017, + "agentfs_batcher_commit_txns": 4784, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9387, + "attr_cache_hits": 25922, + "attr_cache_misses": 29051, + "base_fast_inode_invalidations": 15027, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create": 2, + "connection_reuse": 31210, + "connection_wait_count": 31212, + "connection_wait_nanos": 10156344, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 95, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 7, + "fuse_adapter_entry_misses": 6410, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5283, + "fuse_adapter_negative_misses": 6410, + "fuse_callback_count": 21785, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 38, + "fuse_dispatch_wait_count": 38, + "fuse_dispatch_wait_nanos": 377825, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52561419, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5029, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11700, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 493672925, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 549158, + "fuse_op_getattr_count": 5029, + "fuse_op_getattr_nanos": 53374165, + "fuse_op_lookup_count": 11700, + "fuse_op_lookup_nanos": 131275099, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 98313740, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27550934, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1954420, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1180629003, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 27691889, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16151, + "fuse_read_lane_wait_nanos": 2491830, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32009, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1469827, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6776, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45064, + "negative_lookup_count": 14254, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32899, + "path_resolution_count": 7117, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1555852 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15027, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 33, + "agentfs_batcher_commit_latency_ns_total": 849429017, + "agentfs_batcher_commit_txns": 4784, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9387, + "attr_cache_hits": 25922, + "attr_cache_misses": 29051, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create": 2, + "connection_reuse": 31210, + "connection_wait_count": 31212, + "connection_wait_nanos": 10156344, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6776, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45064, + "negative_lookup_count": 14254, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32899, + "path_resolution_count": 7117, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1555852 + }, + "fuse": { + "fuse_adapter_attr_hits": 95, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 7, + "fuse_adapter_entry_misses": 6410, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5283, + "fuse_adapter_negative_misses": 6410, + "fuse_callback_count": 21785, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 38, + "fuse_dispatch_wait_count": 38, + "fuse_dispatch_wait_nanos": 377825, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52561419, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5029, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11700, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 493672925, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 549158, + "fuse_op_getattr_count": 5029, + "fuse_op_getattr_nanos": 53374165, + "fuse_op_lookup_count": 11700, + "fuse_op_lookup_nanos": 131275099, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 98313740, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27550934, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1954420, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1180629003, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 27691889, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16151, + "fuse_read_lane_wait_nanos": 2491830, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 32009, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1469827 + } + }, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 852920699, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4739, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9401, + "attr_cache_hits": 26134, + "attr_cache_misses": 30797, + "base_fast_inode_invalidations": 15078, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 8, + "connection_reuse": 31835, + "connection_wait_count": 31843, + "connection_wait_nanos": 10395284, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4686, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 9, + "fuse_adapter_entry_misses": 6445, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5296, + "fuse_adapter_negative_misses": 6445, + "fuse_callback_count": 27113, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 45, + "fuse_dispatch_wait_count": 45, + "fuse_dispatch_wait_nanos": 453111, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4739, + "fuse_flush_ranges": 4739, + "fuse_getattr_count": 10286, + "fuse_ino_file_resolutions": 22, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11750, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 494677865, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 617283, + "fuse_op_getattr_count": 10286, + "fuse_op_getattr_nanos": 70512423, + "fuse_op_lookup_count": 11750, + "fuse_op_lookup_nanos": 131888707, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 101202558, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 30045925, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3137443, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1184177964, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 27835189, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16881, + "fuse_read_lane_wait_nanos": 2599410, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37375, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1476788, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20694, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45212, + "negative_lookup_count": 14340, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33023, + "path_resolution_count": 7162, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2344637 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15078, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 852920699, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4739, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9401, + "attr_cache_hits": 26134, + "attr_cache_misses": 30797, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create": 8, + "connection_reuse": 31835, + "connection_wait_count": 31843, + "connection_wait_nanos": 10395284, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "getattr_count": 20694, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6810, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45212, + "negative_lookup_count": 14340, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33023, + "path_resolution_count": 7162, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2344637 + }, + "fuse": { + "fuse_adapter_attr_hits": 4686, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 9, + "fuse_adapter_entry_misses": 6445, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5296, + "fuse_adapter_negative_misses": 6445, + "fuse_callback_count": 27113, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 45, + "fuse_dispatch_wait_count": 45, + "fuse_dispatch_wait_nanos": 453111, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4739, + "fuse_flush_ranges": 4739, + "fuse_getattr_count": 10286, + "fuse_ino_file_resolutions": 22, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11750, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 494677865, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 617283, + "fuse_op_getattr_count": 10286, + "fuse_op_getattr_nanos": 70512423, + "fuse_op_lookup_count": 11750, + "fuse_op_lookup_nanos": 131888707, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 101202558, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 30045925, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3137443, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1184177964, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 27835189, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16881, + "fuse_read_lane_wait_nanos": 2599410, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 37375, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1476788 + } + }, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 856941962, + "agentfs_batcher_commit_txns": 4798, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9405, + "attr_cache_hits": 26154, + "attr_cache_misses": 30831, + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33539, + "connection_wait_count": 33547, + "connection_wait_nanos": 10822420, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6050, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30179, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11657, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12042, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11657, + "fuse_op_getattr_nanos": 72122140, + "fuse_op_lookup_count": 12042, + "fuse_op_lookup_nanos": 136959168, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 103947039, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 31918629, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1191511883, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 29296405, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17877, + "fuse_read_lane_wait_nanos": 2714907, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43211, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1501536, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20720, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13316107 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 856941962, + "agentfs_batcher_commit_txns": 4798, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9405, + "attr_cache_hits": 26154, + "attr_cache_misses": 30831, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33539, + "connection_wait_count": 33547, + "connection_wait_nanos": 10822420, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20720, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14918, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13316107 + }, + "fuse": { + "fuse_adapter_attr_hits": 6050, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30179, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11657, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12042, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11657, + "fuse_op_getattr_nanos": 72122140, + "fuse_op_lookup_count": 12042, + "fuse_op_lookup_nanos": 136959168, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 103947039, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 31918629, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1191511883, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 29296405, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17877, + "fuse_read_lane_wait_nanos": 2714907, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43211, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1501536 + } + }, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 856941962, + "agentfs_batcher_commit_txns": 4798, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9405, + "attr_cache_hits": 26155, + "attr_cache_misses": 30831, + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33547, + "connection_wait_count": 33555, + "connection_wait_nanos": 10834723, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6119, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 30255, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11726, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11726, + "fuse_op_getattr_nanos": 72194096, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 136987946, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 103947039, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1191511883, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 29296405, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17879, + "fuse_read_lane_wait_nanos": 2715824, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43287, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1501536, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20720, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13316107 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 856941962, + "agentfs_batcher_commit_txns": 4798, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4745, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9405, + "attr_cache_hits": 26155, + "attr_cache_misses": 30831, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33547, + "connection_wait_count": 33555, + "connection_wait_nanos": 10834723, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "getattr_count": 20720, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 13316107 + }, + "fuse": { + "fuse_adapter_attr_hits": 6119, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 30255, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4745, + "fuse_flush_ranges": 4745, + "fuse_getattr_count": 11726, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11726, + "fuse_op_getattr_nanos": 72194096, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 136987946, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 103947039, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1191511883, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 29296405, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17879, + "fuse_read_lane_wait_nanos": 2715824, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43287, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1501536 + } + }, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26155, + "attr_cache_misses": 30855, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33579, + "connection_wait_count": 33587, + "connection_wait_nanos": 10852623, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6135, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 30279, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 11742, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11742, + "fuse_op_getattr_nanos": 72225745, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 136987946, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 104822008, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17887, + "fuse_read_lane_wait_nanos": 2718743, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43327, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20736, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14036918 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26155, + "attr_cache_misses": 30855, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33579, + "connection_wait_count": 33587, + "connection_wait_nanos": 10852623, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "getattr_count": 20736, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6815, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14036918 + }, + "fuse": { + "fuse_adapter_attr_hits": 6135, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 30279, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 11742, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 11742, + "fuse_op_getattr_nanos": 72225745, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 136987946, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 104822008, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17887, + "fuse_read_lane_wait_nanos": 2718743, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43327, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126 + } + }, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26155, + "attr_cache_misses": 30855, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33579, + "connection_wait_count": 33587, + "connection_wait_nanos": 10852623, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7996, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5343, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 32186, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13603, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12089, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13603, + "fuse_op_getattr_nanos": 73425960, + "fuse_op_lookup_count": 12089, + "fuse_op_lookup_nanos": 137054814, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 104836240, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17887, + "fuse_read_lane_wait_nanos": 2718743, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45246, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20736, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6861, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14036918 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26155, + "attr_cache_misses": 30855, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33579, + "connection_wait_count": 33587, + "connection_wait_nanos": 10852623, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "getattr_count": 20736, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6861, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46087, + "negative_lookup_count": 14918, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37629, + "path_resolution_count": 8139, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 14036918 + }, + "fuse": { + "fuse_adapter_attr_hits": 7996, + "fuse_adapter_attr_misses": 5607, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6735, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5343, + "fuse_adapter_negative_misses": 6735, + "fuse_callback_count": 32186, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13603, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12089, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13603, + "fuse_op_getattr_nanos": 73425960, + "fuse_op_lookup_count": 12089, + "fuse_op_lookup_nanos": 137054814, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 104836240, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33880030, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 65729823, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17887, + "fuse_read_lane_wait_nanos": 2718743, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45246, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126 + } + }, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26158, + "attr_cache_misses": 30857, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33603, + "connection_wait_count": 33611, + "connection_wait_nanos": 10879680, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8040, + "fuse_adapter_attr_misses": 5608, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6739, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5587, + "fuse_adapter_negative_misses": 6739, + "fuse_callback_count": 32499, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13648, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12337, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13648, + "fuse_op_getattr_nanos": 73535957, + "fuse_op_lookup_count": 12337, + "fuse_op_lookup_nanos": 137485172, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 104851104, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34270757, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 67283387, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2725300, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45595, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20738, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7105, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46096, + "negative_lookup_count": 14920, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 14543192 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26158, + "attr_cache_misses": 30857, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33603, + "connection_wait_count": 33611, + "connection_wait_nanos": 10879680, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "getattr_count": 20738, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7105, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46096, + "negative_lookup_count": 14920, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 14543192 + }, + "fuse": { + "fuse_adapter_attr_hits": 8040, + "fuse_adapter_attr_misses": 5608, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6739, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5587, + "fuse_adapter_negative_misses": 6739, + "fuse_callback_count": 32499, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13648, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12337, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13648, + "fuse_op_getattr_nanos": 73535957, + "fuse_op_lookup_count": 12337, + "fuse_op_lookup_nanos": 137485172, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 104851104, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34270757, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 67283387, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2725300, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45595, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1503126 + } + }, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26158, + "attr_cache_misses": 30857, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33606, + "connection_wait_count": 33614, + "connection_wait_nanos": 10887566, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8040, + "fuse_adapter_attr_misses": 5608, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6739, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5587, + "fuse_adapter_negative_misses": 6739, + "fuse_callback_count": 32499, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13648, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12337, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13648, + "fuse_op_getattr_nanos": 73535957, + "fuse_op_lookup_count": 12337, + "fuse_op_lookup_nanos": 137485172, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 104851104, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34270757, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 67283387, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2725300, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45595, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1504628, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20738, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7105, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46096, + "negative_lookup_count": 14920, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 14679037 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 858457715, + "agentfs_batcher_commit_txns": 4806, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4753, + "agentfs_batcher_pending_max_bytes": 1441792, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9413, + "attr_cache_hits": 26158, + "attr_cache_misses": 30857, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create": 8, + "connection_reuse": 33606, + "connection_wait_count": 33614, + "connection_wait_nanos": 10887566, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "getattr_count": 20738, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7105, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46096, + "negative_lookup_count": 14920, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37671, + "path_resolution_count": 8149, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 14679037 + }, + "fuse": { + "fuse_adapter_attr_hits": 8040, + "fuse_adapter_attr_misses": 5608, + "fuse_adapter_entry_hits": 11, + "fuse_adapter_entry_misses": 6739, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5587, + "fuse_adapter_negative_misses": 6739, + "fuse_callback_count": 32499, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 585484, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4753, + "fuse_flush_ranges": 4753, + "fuse_getattr_count": 13648, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12337, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 495428892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 978, + "fuse_op_forget_count": 47, + "fuse_op_forget_nanos": 681501, + "fuse_op_getattr_count": 13648, + "fuse_op_getattr_nanos": 73535957, + "fuse_op_lookup_count": 12337, + "fuse_op_lookup_nanos": 137485172, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1311, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 104851104, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34270757, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 67283387, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1194285047, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 29832074, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17902, + "fuse_read_lane_wait_nanos": 2725300, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45595, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1504628 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 64596, + "stderr_tail": "en_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":39,\"agentfs_batcher_commit_latency_ns_total\":858457715,\"agentfs_batcher_commit_txns\":4806,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":17,\"agentfs_batcher_enqueues\":4753,\"agentfs_batcher_pending_max_bytes\":1441792,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9413,\"attr_cache_hits\":26155,\"attr_cache_misses\":30855,\"chunk_read_chunks\":267,\"chunk_read_queries\":122,\"chunk_write_chunks\":1003,\"connection_create\":8,\"connection_reuse\":33579,\"connection_wait_count\":33587,\"connection_wait_nanos\":10852623,\"dentry_cache_hits\":26126,\"dentry_cache_misses\":14052,\"getattr_count\":20736,\"lookup_base_count\":23,\"lookup_count\":41052,\"lookup_delta_count\":7547,\"lookup_whiteout_count\":0,\"negative_cache_hits\":6861,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46087,\"negative_lookup_count\":14918,\"path_cache_hits\":26126,\"path_cache_misses\":14052,\"path_component_count\":37629,\"path_resolution_count\":8139,\"readdir_count\":1,\"readdir_plus_count\":1398,\"wal_checkpoint_count\":708,\"wal_checkpoint_nanos\":14036918},\"fuse\":{\"fuse_adapter_attr_hits\":7996,\"fuse_adapter_attr_misses\":5607,\"fuse_adapter_entry_hits\":11,\"fuse_adapter_entry_misses\":6735,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5343,\"fuse_adapter_negative_misses\":6735,\"fuse_callback_count\":32186,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":6,\"fuse_dispatch_parallel_tasks\":49,\"fuse_dispatch_wait_count\":49,\"fuse_dispatch_wait_nanos\":585484,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4753,\"fuse_flush_ranges\":4753,\"fuse_getattr_count\":13603,\"fuse_ino_file_resolutions\":25,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12089,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":495428892,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":978,\"fuse_op_forget_count\":47,\"fuse_op_forget_nanos\":681501,\"fuse_op_getattr_count\":13603,\"fuse_op_getattr_nanos\":73425960,\"fuse_op_lookup_count\":12089,\"fuse_op_lookup_nanos\":137054814,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1311,\"fuse_op_other_count\":3580,\"fuse_op_other_nanos\":104836240,\"fuse_op_read_count\":239,\"fuse_op_read_nanos\":33880030,\"fuse_op_readdirplus_count\":1407,\"fuse_op_readdirplus_nanos\":65729823,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1194285047,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":29832074,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":239,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17887,\"fuse_read_lane_wait_nanos\":2718743,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1407,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45246,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1503126}},\"source\":\"phase-checkpoint-6\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":39,\"agentfs_batcher_commit_latency_ns_total\":858457715,\"agentfs_batcher_commit_txns\":4806,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":17,\"agentfs_batcher_enqueues\":4753,\"agentfs_batcher_pending_max_bytes\":1441792,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9413,\"attr_cache_hits\":26158,\"attr_cache_misses\":30857,\"base_fast_inode_invalidations\":15118,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create\":8,\"connection_reuse\":33603,\"connection_wait_count\":33611,\"connection_wait_nanos\":10879680,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":8040,\"fuse_adapter_attr_misses\":5608,\"fuse_adapter_entry_hits\":11,\"fuse_adapter_entry_misses\":6739,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5587,\"fuse_adapter_negative_misses\":6739,\"fuse_callback_count\":32499,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":6,\"fuse_dispatch_parallel_tasks\":49,\"fuse_dispatch_wait_count\":49,\"fuse_dispatch_wait_nanos\":585484,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4753,\"fuse_flush_ranges\":4753,\"fuse_getattr_count\":13648,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12337,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":495428892,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":978,\"fuse_op_forget_count\":47,\"fuse_op_forget_nanos\":681501,\"fuse_op_getattr_count\":13648,\"fuse_op_getattr_nanos\":73535957,\"fuse_op_lookup_count\":12337,\"fuse_op_lookup_nanos\":137485172,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1311,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":104851104,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34270757,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":67283387,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1194285047,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":29832074,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17902,\"fuse_read_lane_wait_nanos\":2725300,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45595,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1503126,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20738,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7105,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46096,\"negative_lookup_count\":14920,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37671,\"path_resolution_count\":8149,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":717,\"wal_checkpoint_nanos\":14543192},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":15118,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":39,\"agentfs_batcher_commit_latency_ns_total\":858457715,\"agentfs_batcher_commit_txns\":4806,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":17,\"agentfs_batcher_enqueues\":4753,\"agentfs_batcher_pending_max_bytes\":1441792,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9413,\"attr_cache_hits\":26158,\"attr_cache_misses\":30857,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create\":8,\"connection_reuse\":33603,\"connection_wait_count\":33611,\"connection_wait_nanos\":10879680,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"getattr_count\":20738,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7105,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46096,\"negative_lookup_count\":14920,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37671,\"path_resolution_count\":8149,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":717,\"wal_checkpoint_nanos\":14543192},\"fuse\":{\"fuse_adapter_attr_hits\":8040,\"fuse_adapter_attr_misses\":5608,\"fuse_adapter_entry_hits\":11,\"fuse_adapter_entry_misses\":6739,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5587,\"fuse_adapter_negative_misses\":6739,\"fuse_callback_count\":32499,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":6,\"fuse_dispatch_parallel_tasks\":49,\"fuse_dispatch_wait_count\":49,\"fuse_dispatch_wait_nanos\":585484,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4753,\"fuse_flush_ranges\":4753,\"fuse_getattr_count\":13648,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12337,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":495428892,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":978,\"fuse_op_forget_count\":47,\"fuse_op_forget_nanos\":681501,\"fuse_op_getattr_count\":13648,\"fuse_op_getattr_nanos\":73535957,\"fuse_op_lookup_count\":12337,\"fuse_op_lookup_nanos\":137485172,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1311,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":104851104,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34270757,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":67283387,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1194285047,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":29832074,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17902,\"fuse_read_lane_wait_nanos\":2725300,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45595,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1503126}},\"source\":\"phase-checkpoint-7\"}\n\nSession: git-workload-fa418f52ee1a41beabeb538335d5466e\n\nTo resume this session:\n agentfs run --session git-workload-fa418f52ee1a41beabeb538335d5466e\n\nTo see what changed:\n agentfs diff git-workload-fa418f52ee1a41beabeb538335d5466e\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":39,\"agentfs_batcher_commit_latency_ns_total\":858457715,\"agentfs_batcher_commit_txns\":4806,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":17,\"agentfs_batcher_enqueues\":4753,\"agentfs_batcher_pending_max_bytes\":1441792,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9413,\"attr_cache_hits\":26158,\"attr_cache_misses\":30857,\"base_fast_inode_invalidations\":15118,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create\":8,\"connection_reuse\":33606,\"connection_wait_count\":33614,\"connection_wait_nanos\":10887566,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":8040,\"fuse_adapter_attr_misses\":5608,\"fuse_adapter_entry_hits\":11,\"fuse_adapter_entry_misses\":6739,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5587,\"fuse_adapter_negative_misses\":6739,\"fuse_callback_count\":32499,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":6,\"fuse_dispatch_parallel_tasks\":49,\"fuse_dispatch_wait_count\":49,\"fuse_dispatch_wait_nanos\":585484,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4753,\"fuse_flush_ranges\":4753,\"fuse_getattr_count\":13648,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12337,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":495428892,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":978,\"fuse_op_forget_count\":47,\"fuse_op_forget_nanos\":681501,\"fuse_op_getattr_count\":13648,\"fuse_op_getattr_nanos\":73535957,\"fuse_op_lookup_count\":12337,\"fuse_op_lookup_nanos\":137485172,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1311,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":104851104,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34270757,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":67283387,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1194285047,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":29832074,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17902,\"fuse_read_lane_wait_nanos\":2725300,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45595,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1504628,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20738,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7105,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46096,\"negative_lookup_count\":14920,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37671,\"path_resolution_count\":8149,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":719,\"wal_checkpoint_nanos\":14679037},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":15118,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":39,\"agentfs_batcher_commit_latency_ns_total\":858457715,\"agentfs_batcher_commit_txns\":4806,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":17,\"agentfs_batcher_enqueues\":4753,\"agentfs_batcher_pending_max_bytes\":1441792,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9413,\"attr_cache_hits\":26158,\"attr_cache_misses\":30857,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create\":8,\"connection_reuse\":33606,\"connection_wait_count\":33614,\"connection_wait_nanos\":10887566,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"getattr_count\":20738,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7105,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46096,\"negative_lookup_count\":14920,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37671,\"path_resolution_count\":8149,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":719,\"wal_checkpoint_nanos\":14679037},\"fuse\":{\"fuse_adapter_attr_hits\":8040,\"fuse_adapter_attr_misses\":5608,\"fuse_adapter_entry_hits\":11,\"fuse_adapter_entry_misses\":6739,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5587,\"fuse_adapter_negative_misses\":6739,\"fuse_callback_count\":32499,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":6,\"fuse_dispatch_parallel_tasks\":49,\"fuse_dispatch_wait_count\":49,\"fuse_dispatch_wait_nanos\":585484,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4753,\"fuse_flush_ranges\":4753,\"fuse_getattr_count\":13648,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12337,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":495428892,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":978,\"fuse_op_forget_count\":47,\"fuse_op_forget_nanos\":681501,\"fuse_op_getattr_count\":13648,\"fuse_op_getattr_nanos\":73535957,\"fuse_op_lookup_count\":12337,\"fuse_op_lookup_nanos\":137485172,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1311,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":104851104,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34270757,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":67283387,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1194285047,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":29832074,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17902,\"fuse_read_lane_wait_nanos\":2725300,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45595,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1504628}},\"source\":\"cli\"}\n", + "stdout_bytes": 18770, + "stdout_tail": "2026-07-04T01:14:27.354700Z INFO agentfs::fuser::session: resolved FUSE dispatch mode: parallel workers=7 queue_capacity=28\n2026-07-04T01:14:27.354980Z INFO agentfs::fuse: advertising FUSE_OVER_IO_URING\n2026-07-04T01:14:27.355248Z INFO agentfs::fuser::uring: starting fuse-over-io_uring queues nr_queues=14 depth=4\n2026-07-04T01:14:29.719595Z WARN agentfs::fuser::request: Request RequestId(50973): Failed to send reply: No such file or directory (os error 2)\n{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.06564354000147432, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work\", \"duration_seconds\": 0.024741028988501057, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work\", \"duration_seconds\": 0.020805346008273773, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work\", \"duration_seconds\": 0.020045437006046996, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.00550819598720409, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work\", \"duration_seconds\": 0.21495337400119752, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work\", \"duration_seconds\": 0.057891393997124396, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-7wytce9p/agentfs-base/mirror.git\", \"/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/agentfs-base\", \"duration_seconds\": 2.8883740519959247, \"returncode\": 0, \"stderr_bytes\": 2340, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\nUpdating files: 37% (1733/4644)\\nUpdating files: 38% (1765/4644)\\nUpdating files: 39% (1812/4644)\\nUpdating files: 40% (1858/4644)\\nUpdating files: 41% (1905/4644)\\nUpdating files: 42% (1951/4644)\\nUpdating files: 43% (1997/4644)\\nUpdating files: 44% (2044/4644)\\nUpdating files: 45% (2090/4644)\\nUpdating files: 46% (2137/4644)\\nUpdating files: 47% (2183/4644)\\nUpdating files: 48% (2230/4644)\\nUpdating files: 49% (2276/4644)\\nUpdating files: 50% (2322/4644)\\nUpdating files: 51% (2369/4644)\\nUpdating files: 52% (2415/4644)\\nUpdating files: 53% (2462/4644)\\nUpdating files: 54% (2508/4644)\\nUpdating files: 55% (2555/4644)\\nUpdating files: 56% (2601/4644)\\nUpdating files: 57% (2648/4644)\\nUpdating files: 58% (2694/4644)\\nUpdating files: 59% (2740/4644)\\nUpdating files: 60% (2787/4644)\\nUpdating files: 61% (2833/4644)\\nUpdating files: 62% (2880/4644)\\nUpdating files: 63% (2926/4644)\\nUpdating files: 64% (2973/4644)\\nUpdating files: 65% (3019/4644)\\nUpdating files: 66% (3066/4644)\\nUpdating files: 67% (3112/4644)\\nUpdating files: 68% (3158/4644)\\nUpdating files: 69% (3205/4644)\\nUpdating files: 70% (3251/4644)\\nUpdating files: 71% (3298/4644)\\nUpdating files: 72% (3344/4644)\\nUpdating files: 73% (3391/4644)\\nUpdating files: 74% (3437/4644)\\nUpdating files: 75% (3483/4644)\\nUpdating files: 76% (3530/4644)\\nUpdating files: 77% (3576/4644)\\nUpdating files: 78% (3623/4644)\\nUpdating files: 78% (3660/4644)\\nUpdating files: 79% (3669/4644)\\nUpdating files: 80% (3716/4644)\\nUpdating files: 81% (3762/4644)\\nUpdating files: 82% (3809/4644)\\nUpdating files: 83% (3855/4644)\\nUpdating files: 84% (3901/4644)\\nUpdating files: 85% (3948/4644)\\nUpdating files: 86% (3994/4644)\\nUpdating files: 87% (4041/4644)\\nUpdating files: 88% (4087/4644)\\nUpdating files: 89% (4134/4644)\\nUpdating files: 90% (4180/4644)\\nUpdating files: 91% (4227/4644)\\nUpdating files: 92% (4273/4644)\\nUpdating files: 93% (4319/4644)\\nUpdating files: 94% (4366/4644)\\nUpdating files: 95% (4412/4644)\\nUpdating files: 96% (4459/4644)\\nUpdating files: 97% (4505/4644)\\nUpdating files: 98% (4552/4644)\\nUpdating files: 99% (4598/4644)\\nUpdating files: 100% (4644/4644)\\nUpdating files: 100% (4644/4644), done.\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work\", \"duration_seconds\": 0.0352727639983641, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work\", \"duration_seconds\": 0.1319450429873541, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.060572469999897294, \"clone\": 2.8884635900030844, \"diff\": 0.06564354000147432, \"edit\": 0.00550819598720409, \"fsck\": 0.21496818200103007, \"read_search\": 0.009809875991777517, \"status\": 0.1672487969917711}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work\", \"duration_seconds\": 0.006677401004708372, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 4.114593252001214}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.06564354000147432, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work", + "duration_seconds": 0.024741028988501057, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work", + "duration_seconds": 0.020805346008273773, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work", + "duration_seconds": 0.020045437006046996, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.00550819598720409, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work", + "duration_seconds": 0.21495337400119752, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work", + "duration_seconds": 0.057891393997124396, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-7wytce9p/agentfs-base/mirror.git", + "/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/agentfs-base", + "duration_seconds": 2.8883740519959247, + "returncode": 0, + "stderr_bytes": 2340, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\nUpdating files: 37% (1733/4644)\nUpdating files: 38% (1765/4644)\nUpdating files: 39% (1812/4644)\nUpdating files: 40% (1858/4644)\nUpdating files: 41% (1905/4644)\nUpdating files: 42% (1951/4644)\nUpdating files: 43% (1997/4644)\nUpdating files: 44% (2044/4644)\nUpdating files: 45% (2090/4644)\nUpdating files: 46% (2137/4644)\nUpdating files: 47% (2183/4644)\nUpdating files: 48% (2230/4644)\nUpdating files: 49% (2276/4644)\nUpdating files: 50% (2322/4644)\nUpdating files: 51% (2369/4644)\nUpdating files: 52% (2415/4644)\nUpdating files: 53% (2462/4644)\nUpdating files: 54% (2508/4644)\nUpdating files: 55% (2555/4644)\nUpdating files: 56% (2601/4644)\nUpdating files: 57% (2648/4644)\nUpdating files: 58% (2694/4644)\nUpdating files: 59% (2740/4644)\nUpdating files: 60% (2787/4644)\nUpdating files: 61% (2833/4644)\nUpdating files: 62% (2880/4644)\nUpdating files: 63% (2926/4644)\nUpdating files: 64% (2973/4644)\nUpdating files: 65% (3019/4644)\nUpdating files: 66% (3066/4644)\nUpdating files: 67% (3112/4644)\nUpdating files: 68% (3158/4644)\nUpdating files: 69% (3205/4644)\nUpdating files: 70% (3251/4644)\nUpdating files: 71% (3298/4644)\nUpdating files: 72% (3344/4644)\nUpdating files: 73% (3391/4644)\nUpdating files: 74% (3437/4644)\nUpdating files: 75% (3483/4644)\nUpdating files: 76% (3530/4644)\nUpdating files: 77% (3576/4644)\nUpdating files: 78% (3623/4644)\nUpdating files: 78% (3660/4644)\nUpdating files: 79% (3669/4644)\nUpdating files: 80% (3716/4644)\nUpdating files: 81% (3762/4644)\nUpdating files: 82% (3809/4644)\nUpdating files: 83% (3855/4644)\nUpdating files: 84% (3901/4644)\nUpdating files: 85% (3948/4644)\nUpdating files: 86% (3994/4644)\nUpdating files: 87% (4041/4644)\nUpdating files: 88% (4087/4644)\nUpdating files: 89% (4134/4644)\nUpdating files: 90% (4180/4644)\nUpdating files: 91% (4227/4644)\nUpdating files: 92% (4273/4644)\nUpdating files: 93% (4319/4644)\nUpdating files: 94% (4366/4644)\nUpdating files: 95% (4412/4644)\nUpdating files: 96% (4459/4644)\nUpdating files: 97% (4505/4644)\nUpdating files: 98% (4552/4644)\nUpdating files: 99% (4598/4644)\nUpdating files: 100% (4644/4644)\nUpdating files: 100% (4644/4644), done.\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work", + "duration_seconds": 0.0352727639983641, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work", + "duration_seconds": 0.1319450429873541, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.060572469999897294, + "clone": 2.8884635900030844, + "diff": 0.06564354000147432, + "edit": 0.00550819598720409, + "fsck": 0.21496818200103007, + "read_search": 0.009809875991777517, + "status": 0.1672487969917711 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/agentfs-base/work", + "duration_seconds": 0.006677401004708372, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 4.114593252001214 + } + }, + "base_tree": { + "after": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "f4984979a2edce91f3ea174919e7b548e210b551c08e64a9da9b8dd028518a5b", + "symlinks": 0 + }, + "before": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "f4984979a2edce91f3ea174919e7b548e210b551c08e64a9da9b8dd028518a5b", + "symlinks": 0 + }, + "unchanged": true + }, + "benchmark": "phase7-git-workload", + "command": { + "agentfs_prefix": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-fa418f52ee1a41beabeb538335d5466e", + "--no-default-allows", + "--" + ], + "argv": [ + "/home/ain3sh/factory/vfs/scripts/validation/git-workload-benchmark.py", + "--output", + "/tmp/vfs-val/misc-a-bench-rebaseline/run3.json.iterations/iter-04.json", + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "workload_argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ] + }, + "correctness": { + "agentfs_backup_verify": true, + "agentfs_base_unchanged": true, + "agentfs_db_inspectable": true, + "agentfs_integrity_require_portable": true, + "agentfs_no_nonempty_sidecars": true, + "agentfs_portable": true, + "agentfs_returncode_zero": true, + "equivalence": { + "agentfs": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + }, + "checked": true, + "equivalent": true, + "native": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + } + }, + "native_returncode_zero": true, + "passed": true, + "performance_passed": false + }, + "database": { + "after": { + "artifacts": [ + { + "bytes": 57143296, + "path": "/tmp/agentfs-git-workload-7wytce9p/home/.agentfs/run/git-workload-fa418f52ee1a41beabeb538335d5466e/delta.db" + } + ], + "path": "/tmp/agentfs-git-workload-7wytce9p/home/.agentfs/run/git-workload-fa418f52ee1a41beabeb538335d5466e/delta.db", + "total_bytes": 57143296 + }, + "backup": { + "artifacts": { + "artifacts": [ + { + "bytes": 57143296, + "path": "/tmp/agentfs-git-workload-7wytce9p/git-workload-backup.db" + } + ], + "path": "/tmp/agentfs-git-workload-7wytce9p/git-workload-backup.db", + "total_bytes": 57143296 + }, + "inspect": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "path": "/tmp/agentfs-git-workload-7wytce9p/git-workload-backup.db", + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "backup", + "/tmp/agentfs-git-workload-7wytce9p/home/.agentfs/run/git-workload-fa418f52ee1a41beabeb538335d5466e/delta.db", + "/tmp/agentfs-git-workload-7wytce9p/git-workload-backup.db", + "--verify" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p", + "duration_seconds": 3.092225314991083, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 0, + "fuse_readdirplus_mode": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_workers_configured": 0, + "fuse_writeback_cache_enabled": 0 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "fuse": { + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 7355, + "stderr_tail": "{\"counters\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_mode\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_workers_configured\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0,\"fuse_writeback_cache_enabled\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":0,\"fuse_readdirplus_mode\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_workers_configured\":0,\"fuse_writeback_cache_enabled\":0},\"core\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"fuse\":{\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0}},\"source\":\"cli\"}\n", + "stdout_bytes": 241, + "stdout_tail": "Source: /tmp/agentfs-git-workload-7wytce9p/home/.agentfs/run/git-workload-fa418f52ee1a41beabeb538335d5466e/delta.db\nBackup: /tmp/agentfs-git-workload-7wytce9p/git-workload-backup.db\nCheckpoint: complete\nCopy: complete\nVerification: complete\n", + "timed_out": false + } + }, + "inspect_after": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "integrity": { + "result": { + "checks": [ + { + "detail": "ok", + "name": "pragma.integrity_check", + "ok": true, + "violating_rows": null + }, + { + "detail": "present", + "name": "schema.table.fs_config", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_symlink", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.kv_store", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.tool_calls", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 0.5", + "name": "config.schema_version", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 65536", + "name": "config.chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 16384", + "name": "config.inline_threshold", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.kind_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_has_no_chunks", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunked_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_size_matches_blob", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.non_regular_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_reference_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunk_length_within_chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 1, expected 1", + "name": "namespace.root_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_is_directory", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_target_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_root_inode_has_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_names_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_directory_nlink_matches_dentries", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.directory_nlink_positive", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.rows_reference_symlink_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.inodes_have_rows", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable: no partial-origin rows", + "name": "overlay.portability_status", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable requirement satisfied", + "name": "overlay.require_portable", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_regular", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_sizes_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_paths_absolute", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_references_partial_origin", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_unique", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_index_in_range", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.whiteout_paths_absolute", + "ok": true, + "violating_rows": 0 + } + ], + "database": "/tmp/agentfs-git-workload-7wytce9p/home/.agentfs/run/git-workload-fa418f52ee1a41beabeb538335d5466e/delta.db", + "ok": true, + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true + }, + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "integrity", + "/tmp/agentfs-git-workload-7wytce9p/home/.agentfs/run/git-workload-fa418f52ee1a41beabeb538335d5466e/delta.db", + "--json", + "--require-portable" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p", + "duration_seconds": 2.6332695890014293, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 0, + "fuse_readdirplus_mode": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_workers_configured": 0, + "fuse_writeback_cache_enabled": 0 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "fuse": { + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 7355, + "stderr_tail": "{\"counters\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_mode\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_workers_configured\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0,\"fuse_writeback_cache_enabled\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":0,\"fuse_readdirplus_mode\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_workers_configured\":0,\"fuse_writeback_cache_enabled\":0},\"core\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"fuse\":{\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0}},\"source\":\"cli\"}\n", + "stdout_bytes": 6395, + "stdout_tail": "{\n \"database\": \"/tmp/agentfs-git-workload-7wytce9p/home/.agentfs/run/git-workload-fa418f52ee1a41beabeb538335d5466e/delta.db\",\n \"ok\": true,\n \"portable\": true,\n \"origin_backed\": false,\n \"partial_origin_rows\": 0,\n \"checks\": [\n {\n \"name\": \"pragma.integrity_check\",\n \"ok\": true,\n \"detail\": \"ok\",\n \"violating_rows\": null\n },\n {\n \"name\": \"schema.table.fs_config\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_inode\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_dentry\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_data\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_symlink\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.kv_store\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.tool_calls\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.schema_version\",\n \"ok\": true,\n \"detail\": \"found 0.5\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.chunk_size\",\n \"ok\": true,\n \"detail\": \"found 65536\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.inline_threshold\",\n \"ok\": true,\n \"detail\": \"found 16384\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.kind_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_has_no_chunks\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunked_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_size_matches_blob\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.non_regular_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_reference_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunk_length_within_chunk_size\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.root_inode\",\n \"ok\": true,\n \"detail\": \"found 1, expected 1\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_is_directory\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_target_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_root_inode_has_dentry\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_names_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_directory_nlink_matches_dentries\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.directory_nlink_positive\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.rows_reference_symlink_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.inodes_have_rows\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.portability_status\",\n \"ok\": true,\n \"detail\": \"portable: no partial-origin rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.require_portable\",\n \"ok\": true,\n \"detail\": \"portable requirement satisfied\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_regular\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_sizes_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_references_partial_origin\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_unique\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_index_in_range\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.whiteout_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n }\n ]\n}\n", + "timed_out": false + } + }, + "nonempty_sidecars": false + }, + "environment": { + "AGENTFS_BIN": "/home/ain3sh/factory/vfs/target/release/agentfs", + "AGENTFS_PROFILE": "1" + }, + "git_commit": "2b811f03493e861e5a7786592d25ac73b39e1aba", + "kept_temp": false, + "native": { + "run": { + "argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/native", + "duration_seconds": 0.9102444470045157, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 15987, + "stdout_tail": "{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.01741337300336454, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/native/work\", \"duration_seconds\": 0.005314395006280392, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/native/work\", \"duration_seconds\": 0.006843777999165468, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/native/work\", \"duration_seconds\": 0.005184065012144856, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.0009361069969600067, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/native/work\", \"duration_seconds\": 0.18900179499178194, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/native/work\", \"duration_seconds\": 0.18161140200390946, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-7wytce9p/native/mirror.git\", \"/tmp/agentfs-git-workload-7wytce9p/native/work\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/native\", \"duration_seconds\": 0.3299830220057629, \"returncode\": 0, \"stderr_bytes\": 149, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-7wytce9p/native/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/native/work\", \"duration_seconds\": 0.010901018991717137, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/native/work\", \"duration_seconds\": 0.11593275199993514, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.18452265500673093, \"clone\": 0.3300862679898273, \"diff\": 0.01741337300336454, \"edit\": 0.0009361069969600067, \"fsck\": 0.1890194620063994, \"read_search\": 0.008877050000592135, \"status\": 0.12686727399704978}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-7wytce9p/native/work\", \"duration_seconds\": 0.004436167990206741, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 0.858039740996901}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.01741337300336454, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/native/work", + "duration_seconds": 0.005314395006280392, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/native/work", + "duration_seconds": 0.006843777999165468, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/native/work", + "duration_seconds": 0.005184065012144856, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.0009361069969600067, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/native/work", + "duration_seconds": 0.18900179499178194, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/native/work", + "duration_seconds": 0.18161140200390946, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-7wytce9p/native/mirror.git", + "/tmp/agentfs-git-workload-7wytce9p/native/work" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/native", + "duration_seconds": 0.3299830220057629, + "returncode": 0, + "stderr_bytes": 149, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-7wytce9p/native/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/native/work", + "duration_seconds": 0.010901018991717137, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/native/work", + "duration_seconds": 0.11593275199993514, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.18452265500673093, + "clone": 0.3300862679898273, + "diff": 0.01741337300336454, + "edit": 0.0009361069969600067, + "fsck": 0.1890194620063994, + "read_search": 0.008877050000592135, + "status": 0.12686727399704978 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-7wytce9p/native/work", + "duration_seconds": 0.004436167990206741, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 0.858039740996901 + } + }, + "parameters": { + "edit_files": 8, + "fixture_dirs": 8, + "fixture_file_size_bytes": 1024, + "fixture_files": 96, + "read_bytes": 4096, + "read_files": 64, + "search_token": "AGENTFS_TOKEN", + "skip_fsck": false, + "timeout_seconds": 180.0 + }, + "schema_version": 1, + "source": { + "kind": "source", + "mirror_head": "7d47056ea42636271ac020b86347fbbef49490aa", + "path": "/home/ain3sh/factory/vfs/.agents/benchmarks/fixtures/codex" + }, + "summary": { + "agentfs_base_unchanged": true, + "agentfs_seconds": 4.114593252001214, + "all_equivalent": true, + "correctness_passed": true, + "native_seconds": 0.858039740996901, + "passed": true, + "performance_passed": false, + "phase_ratios": { + "checkout": { + "agentfs_seconds": 0.060572469999897294, + "native_seconds": 0.18452265500673093, + "ratio": 0.32826576225931586 + }, + "clone": { + "agentfs_seconds": 2.8884635900030844, + "native_seconds": 0.3300862679898273, + "ratio": 8.750632395565457 + }, + "diff": { + "agentfs_seconds": 0.06564354000147432, + "native_seconds": 0.01741337300336454, + "ratio": 3.7697199726205226 + }, + "edit": { + "agentfs_seconds": 0.00550819598720409, + "native_seconds": 0.0009361069969600067, + "ratio": 5.884152137620884 + }, + "fsck": { + "agentfs_seconds": 0.21496818200103007, + "native_seconds": 0.1890194620063994, + "ratio": 1.1372806785036356 + }, + "read_search": { + "agentfs_seconds": 0.009809875991777517, + "native_seconds": 0.008877050000592135, + "ratio": 1.1050828812638387 + }, + "status": { + "agentfs_seconds": 0.1672487969917711, + "native_seconds": 0.12686727399704978, + "ratio": 1.3182973963455726 + } + }, + "ratio": 4.795341119306122, + "threshold_failures": [ + { + "agentfs_seconds": 2.8884635900030844, + "native_seconds": 0.3300862679898273, + "phase": "clone", + "ratio": 8.750632395565457 + }, + { + "agentfs_seconds": 0.06564354000147432, + "native_seconds": 0.01741337300336454, + "phase": "diff", + "ratio": 3.7697199726205226 + }, + { + "agentfs_seconds": 0.00550819598720409, + "native_seconds": 0.0009361069969600067, + "phase": "edit", + "ratio": 5.884152137620884 + } + ] + }, + "temp_dir": "/tmp/agentfs-git-workload-7wytce9p" +} diff --git a/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/warmup-00.json b/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/warmup-00.json new file mode 100644 index 00000000..03ea31cf --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/warmup-00.json @@ -0,0 +1,8674 @@ +{ + "agentfs": { + "bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "db_path": "/tmp/agentfs-git-workload-s58r0ykm/home/.agentfs/run/git-workload-f4f38b250e7f4cfc937dd9957456e3f9/delta.db", + "per_phase_counters": { + "checkpoint_count": 7, + "label_count": 7, + "labels_aligned": true, + "phases": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 962807731, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4732, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29060, + "base_fast_inode_invalidations": 15032, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 971, + "connection_create": 2, + "connection_reuse": 31236, + "connection_wait_count": 31238, + "connection_wait_nanos": 14138406, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 104, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5344, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21857, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 335791, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52591244, + "fuse_flush_count": 4732, + "fuse_flush_ranges": 4732, + "fuse_getattr_count": 5038, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11761, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 557160557, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 37, + "fuse_op_forget_nanos": 432447, + "fuse_op_getattr_count": 5038, + "fuse_op_getattr_nanos": 63853236, + "fuse_op_lookup_count": 11761, + "fuse_op_lookup_nanos": 161440489, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 119093185, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26866715, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2261339, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4750, + "fuse_op_setattr_nanos": 1373845702, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 27901657, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 2, + "fuse_read_lane_wait_count": 16156, + "fuse_read_lane_wait_nanos": 3061810, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32082, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10205, + "fuse_write_lane_wait_nanos": 1879246, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19340, + "lookup_base_count": 24, + "lookup_count": 40290, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6839, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1617016 + }, + "phase": "clone", + "seq": 1 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 2, + "agentfs_batcher_commit_latency_ns_total": 3186372, + "agentfs_batcher_commit_txns": 10, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 5, + "agentfs_batcher_drains_timer": 1, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 13, + "attr_cache_hits": 208, + "attr_cache_misses": 1735, + "base_fast_inode_invalidations": 51, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 11, + "connection_create": 2, + "connection_reuse": 627, + "connection_wait_count": 629, + "connection_wait_nanos": 442453, + "dentry_cache_hits": 73, + "dentry_cache_misses": 56, + "fuse_adapter_attr_hits": 4602, + "fuse_adapter_attr_misses": 660, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 35, + "fuse_adapter_inval_entry_notifications": 10, + "fuse_adapter_inval_inode_notifications": 28, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 13, + "fuse_adapter_negative_misses": 35, + "fuse_callback_count": 5333, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 4, + "fuse_dispatch_parallel_tasks": 9, + "fuse_dispatch_wait_count": 9, + "fuse_dispatch_wait_nanos": 72139, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 643032, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 5262, + "fuse_ino_file_resolutions": 3, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 50, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 9, + "fuse_op_create_nanos": 1074095, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 9, + "fuse_op_forget_nanos": 119404, + "fuse_op_getattr_count": 5262, + "fuse_op_getattr_nanos": 18572017, + "fuse_op_lookup_count": 50, + "fuse_op_lookup_nanos": 619104, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 15, + "fuse_op_other_nanos": 2571584, + "fuse_op_read_count": 8, + "fuse_op_read_nanos": 1021656, + "fuse_op_readdirplus_count": 4, + "fuse_op_readdirplus_nanos": 1041997, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 14, + "fuse_op_setattr_nanos": 3860596, + "fuse_op_write_count": 9, + "fuse_op_write_nanos": 189381, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 8, + "fuse_read_lane_max_concurrent": 2, + "fuse_read_lane_wait_count": 725, + "fuse_read_lane_wait_nanos": 99040, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 4, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5371, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 591393, + "fuse_write_count": 9, + "fuse_write_lane_wait_count": 42, + "fuse_write_lane_wait_nanos": 86753, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 1348, + "lookup_base_count": 0, + "lookup_count": 164, + "lookup_delta_count": 60, + "lookup_whiteout_count": 0, + "negative_cache_hits": 34, + "negative_cache_invalidations": 26, + "negative_cache_misses": 148, + "negative_lookup_count": 86, + "path_cache_hits": 73, + "path_cache_misses": 56, + "path_component_count": 124, + "path_resolution_count": 45, + "readdir_count": 0, + "readdir_plus_count": 4, + "wal_checkpoint_count": 2, + "wal_checkpoint_nanos": 690878 + }, + "phase": "checkout", + "seq": 2 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 4, + "agentfs_batcher_commit_latency_ns_total": 5707640, + "agentfs_batcher_commit_txns": 4, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 2, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 6, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 4, + "attr_cache_hits": 19, + "attr_cache_misses": 33, + "base_fast_inode_invalidations": 24, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 20, + "chunk_read_queries": 12, + "chunk_write_chunks": 20, + "connection_create": 0, + "connection_reuse": 1704, + "connection_wait_count": 1704, + "connection_wait_nanos": 412529, + "dentry_cache_hits": 19, + "dentry_cache_misses": 570, + "fuse_adapter_attr_hits": 1392, + "fuse_adapter_attr_misses": 6, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 289, + "fuse_adapter_inval_entry_notifications": 4, + "fuse_adapter_inval_inode_notifications": 8, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 1, + "fuse_adapter_negative_misses": 289, + "fuse_callback_count": 3093, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 3, + "fuse_dispatch_wait_count": 3, + "fuse_dispatch_wait_nanos": 36968, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 1181458, + "fuse_flush_count": 6, + "fuse_flush_ranges": 6, + "fuse_getattr_count": 1398, + "fuse_ino_file_resolutions": 2, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 292, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 2, + "fuse_op_create_nanos": 820535, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 3, + "fuse_op_forget_nanos": 52907, + "fuse_op_getattr_count": 1398, + "fuse_op_getattr_nanos": 1535484, + "fuse_op_lookup_count": 292, + "fuse_op_lookup_nanos": 4757661, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 2762, + "fuse_op_other_nanos": 2479289, + "fuse_op_read_count": 12, + "fuse_op_read_nanos": 1345866, + "fuse_op_readdirplus_count": 1381, + "fuse_op_readdirplus_nanos": 58462869, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 6, + "fuse_op_setattr_nanos": 10504580, + "fuse_op_write_count": 10, + "fuse_op_write_nanos": 2476932, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 12, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 995, + "fuse_read_lane_wait_nanos": 103587, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1381, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5863, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 1181458, + "fuse_write_count": 10, + "fuse_write_lane_wait_count": 14, + "fuse_write_lane_wait_nanos": 3933, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 24, + "lookup_base_count": 0, + "lookup_count": 598, + "lookup_delta_count": 295, + "lookup_whiteout_count": 0, + "negative_cache_hits": 5, + "negative_cache_invalidations": 4, + "negative_cache_misses": 873, + "negative_lookup_count": 578, + "path_cache_hits": 19, + "path_cache_misses": 570, + "path_component_count": 4606, + "path_resolution_count": 977, + "readdir_count": 0, + "readdir_plus_count": 1376, + "wal_checkpoint_count": 688, + "wal_checkpoint_nanos": 9100339 + }, + "phase": "status", + "seq": 3 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 1, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 8, + "connection_wait_count": 8, + "connection_wait_nanos": 5351, + "dentry_cache_hits": 1, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 69, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 1, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 1, + "fuse_callback_count": 76, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 69, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 1, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 69, + "fuse_op_getattr_nanos": 87840, + "fuse_op_lookup_count": 1, + "fuse_op_lookup_nanos": 26898, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 6, + "fuse_op_read_nanos": 1386270, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 6, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 2, + "fuse_read_lane_wait_nanos": 967, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 76, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 2, + "lookup_delta_count": 1, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 2, + "negative_lookup_count": 0, + "path_cache_hits": 1, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "read_search", + "seq": 4 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 4257716, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 8, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 8, + "attr_cache_hits": 0, + "attr_cache_misses": 24, + "base_fast_inode_invalidations": 16, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 32, + "connection_wait_count": 32, + "connection_wait_nanos": 67551, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 16, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 24, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 6398, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 16, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 16, + "fuse_op_getattr_nanos": 74192, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 8, + "fuse_op_other_nanos": 2499248, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 8, + "fuse_op_setattr_nanos": 8330735, + "fuse_op_write_count": 8, + "fuse_op_write_nanos": 1736134, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 8, + "fuse_read_lane_wait_nanos": 13220, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 40, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 6398, + "fuse_write_count": 8, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 7593, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 16, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 8, + "wal_checkpoint_nanos": 1979685 + }, + "phase": "edit", + "seq": 5 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 1939, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 76, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 2015, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 1939, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 76, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 1939, + "fuse_op_getattr_nanos": 2715548, + "fuse_op_lookup_count": 76, + "fuse_op_lookup_nanos": 98298, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 12, + "fuse_op_other_nanos": 8674, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 2027, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 76, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "diff", + "seq": 6 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 3, + "attr_cache_misses": 2, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 1, + "chunk_read_queries": 1, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 24, + "connection_wait_count": 24, + "connection_wait_nanos": 15701, + "dentry_cache_hits": 4, + "dentry_cache_misses": 2, + "fuse_adapter_attr_hits": 44, + "fuse_adapter_attr_misses": 1, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 4, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 241, + "fuse_adapter_negative_misses": 4, + "fuse_callback_count": 310, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 45, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 245, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 45, + "fuse_op_getattr_nanos": 82681, + "fuse_op_lookup_count": 245, + "fuse_op_lookup_nanos": 360264, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 36, + "fuse_op_other_nanos": 13432, + "fuse_op_read_count": 2, + "fuse_op_read_nanos": 319168, + "fuse_op_readdirplus_count": 18, + "fuse_op_readdirplus_nanos": 1114504, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 2, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 15, + "fuse_read_lane_wait_nanos": 4730, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 18, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 346, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 2, + "lookup_base_count": 0, + "lookup_count": 8, + "lookup_delta_count": 4, + "lookup_whiteout_count": 0, + "negative_cache_hits": 241, + "negative_cache_invalidations": 0, + "negative_cache_misses": 9, + "negative_lookup_count": 2, + "path_cache_hits": 4, + "path_cache_misses": 2, + "path_component_count": 42, + "path_resolution_count": 10, + "readdir_count": 0, + "readdir_plus_count": 18, + "wal_checkpoint_count": 9, + "wal_checkpoint_nanos": 342185 + }, + "phase": "fsck", + "seq": 7 + } + ] + }, + "profile_counters": { + "last_by_source": { + "cli": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26152, + "attr_cache_misses": 30854, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33634, + "connection_wait_count": 33638, + "connection_wait_nanos": 15088402, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8166, + "fuse_adapter_attr_misses": 5601, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5675, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32708, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13767, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12425, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13767, + "fuse_op_getattr_nanos": 86920998, + "fuse_op_lookup_count": 12425, + "fuse_op_lookup_nanos": 167302714, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 126665412, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 30939675, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 62880709, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3283354, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45805, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10271, + "fuse_write_lane_wait_nanos": 1978981, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20730, + "lookup_base_count": 24, + "lookup_count": 41062, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7195, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 13907880 + }, + "phase-checkpoint-1": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 962807731, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4732, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29060, + "base_fast_inode_invalidations": 15032, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 971, + "connection_create": 2, + "connection_reuse": 31236, + "connection_wait_count": 31238, + "connection_wait_nanos": 14138406, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 104, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5344, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21857, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 335791, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52591244, + "fuse_flush_count": 4732, + "fuse_flush_ranges": 4732, + "fuse_getattr_count": 5038, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11761, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 557160557, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 37, + "fuse_op_forget_nanos": 432447, + "fuse_op_getattr_count": 5038, + "fuse_op_getattr_nanos": 63853236, + "fuse_op_lookup_count": 11761, + "fuse_op_lookup_nanos": 161440489, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 119093185, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26866715, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2261339, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4750, + "fuse_op_setattr_nanos": 1373845702, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 27901657, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 2, + "fuse_read_lane_wait_count": 16156, + "fuse_read_lane_wait_nanos": 3061810, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32082, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10205, + "fuse_write_lane_wait_nanos": 1879246, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19340, + "lookup_base_count": 24, + "lookup_count": 40290, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6839, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1617016 + }, + "phase-checkpoint-2": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 965994103, + "agentfs_batcher_commit_txns": 4814, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9398, + "attr_cache_hits": 26129, + "attr_cache_misses": 30795, + "base_fast_inode_invalidations": 15083, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 4, + "connection_reuse": 31863, + "connection_wait_count": 31867, + "connection_wait_nanos": 14580859, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4706, + "fuse_adapter_attr_misses": 5594, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27190, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 407930, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10300, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11811, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 558234652, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 551851, + "fuse_op_getattr_count": 10300, + "fuse_op_getattr_nanos": 82425253, + "fuse_op_lookup_count": 11811, + "fuse_op_lookup_nanos": 162059593, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 121664769, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 27888371, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3303336, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4764, + "fuse_op_setattr_nanos": 1377706298, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 28091038, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 16881, + "fuse_read_lane_wait_nanos": 3160850, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37453, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10247, + "fuse_write_lane_wait_nanos": 1965999, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20688, + "lookup_base_count": 24, + "lookup_count": 40454, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6873, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2307894 + }, + "phase-checkpoint-3": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 971701743, + "agentfs_batcher_commit_txns": 4818, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26148, + "attr_cache_misses": 30828, + "base_fast_inode_invalidations": 15107, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33567, + "connection_wait_count": 33571, + "connection_wait_nanos": 14993388, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6098, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30283, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11698, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12103, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11698, + "fuse_op_getattr_nanos": 83960737, + "fuse_op_lookup_count": 12103, + "fuse_op_lookup_nanos": 166817254, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 124144058, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 29234237, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4770, + "fuse_op_setattr_nanos": 1388210878, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30567970, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17876, + "fuse_read_lane_wait_nanos": 3264437, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43316, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10261, + "fuse_write_lane_wait_nanos": 1969932, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20712, + "lookup_base_count": 24, + "lookup_count": 41052, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11408233 + }, + "phase-checkpoint-4": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 971701743, + "agentfs_batcher_commit_txns": 4818, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26149, + "attr_cache_misses": 30828, + "base_fast_inode_invalidations": 15107, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33575, + "connection_wait_count": 33579, + "connection_wait_nanos": 14998739, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6167, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30359, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11767, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12104, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11767, + "fuse_op_getattr_nanos": 84048577, + "fuse_op_lookup_count": 12104, + "fuse_op_lookup_nanos": 166844152, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 124144058, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4770, + "fuse_op_setattr_nanos": 1388210878, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30567970, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17878, + "fuse_read_lane_wait_nanos": 3265404, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43392, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10261, + "fuse_write_lane_wait_nanos": 1969932, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20712, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11408233 + }, + "phase-checkpoint-5": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26149, + "attr_cache_misses": 30852, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33607, + "connection_wait_count": 33611, + "connection_wait_nanos": 15066290, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6183, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30383, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11783, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12104, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11783, + "fuse_op_getattr_nanos": 84122769, + "fuse_op_lookup_count": 12104, + "fuse_op_lookup_nanos": 166844152, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 126643306, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17886, + "fuse_read_lane_wait_nanos": 3278624, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43432, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20728, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 13387918 + }, + "phase-checkpoint-6": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26149, + "attr_cache_misses": 30852, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33607, + "connection_wait_count": 33611, + "connection_wait_nanos": 15066290, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 8122, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5434, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32398, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13722, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12180, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13722, + "fuse_op_getattr_nanos": 86838317, + "fuse_op_lookup_count": 12180, + "fuse_op_lookup_nanos": 166942450, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 126651980, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17886, + "fuse_read_lane_wait_nanos": 3278624, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45459, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20728, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6954, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 13387918 + }, + "phase-checkpoint-7": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26152, + "attr_cache_misses": 30854, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33631, + "connection_wait_count": 33635, + "connection_wait_nanos": 15081991, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8166, + "fuse_adapter_attr_misses": 5601, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5675, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32708, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13767, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12425, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13767, + "fuse_op_getattr_nanos": 86920998, + "fuse_op_lookup_count": 12425, + "fuse_op_lookup_nanos": 167302714, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 126665412, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 30939675, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 62880709, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3283354, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45805, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20730, + "lookup_base_count": 24, + "lookup_count": 41062, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7195, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 13730103 + } + }, + "max_counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26152, + "attr_cache_misses": 30854, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33634, + "connection_wait_count": 33638, + "connection_wait_nanos": 15088402, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8166, + "fuse_adapter_attr_misses": 5601, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5675, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32708, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13767, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12425, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13767, + "fuse_op_getattr_nanos": 86920998, + "fuse_op_lookup_count": 12425, + "fuse_op_lookup_nanos": 167302714, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 126665412, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 30939675, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 62880709, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3283354, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45805, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10271, + "fuse_write_lane_wait_nanos": 1978981, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20730, + "lookup_base_count": 24, + "lookup_count": 41062, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7195, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 13907880 + }, + "summary_count": 8 + }, + "profile_enabled": true, + "profile_summary_count": 8, + "session": "git-workload-f4f38b250e7f4cfc937dd9957456e3f9" + }, + "agentfs_overlay": { + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 962807731, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4732, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29060, + "base_fast_inode_invalidations": 15032, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 971, + "connection_create": 2, + "connection_reuse": 31236, + "connection_wait_count": 31238, + "connection_wait_nanos": 14138406, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 104, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5344, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21857, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 335791, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52591244, + "fuse_flush_count": 4732, + "fuse_flush_ranges": 4732, + "fuse_getattr_count": 5038, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11761, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 557160557, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 37, + "fuse_op_forget_nanos": 432447, + "fuse_op_getattr_count": 5038, + "fuse_op_getattr_nanos": 63853236, + "fuse_op_lookup_count": 11761, + "fuse_op_lookup_nanos": 161440489, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 119093185, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26866715, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2261339, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4750, + "fuse_op_setattr_nanos": 1373845702, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 27901657, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 2, + "fuse_read_lane_wait_count": 16156, + "fuse_read_lane_wait_nanos": 3061810, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32082, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10205, + "fuse_write_lane_wait_nanos": 1879246, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19340, + "lookup_base_count": 24, + "lookup_count": 40290, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6839, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1617016 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15032, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 962807731, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4732, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29060, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 971, + "connection_create": 2, + "connection_reuse": 31236, + "connection_wait_count": 31238, + "connection_wait_nanos": 14138406, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "getattr_count": 19340, + "lookup_base_count": 24, + "lookup_count": 40290, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6839, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1617016 + }, + "fuse": { + "fuse_adapter_attr_hits": 104, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5344, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21857, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 335791, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52591244, + "fuse_flush_count": 4732, + "fuse_flush_ranges": 4732, + "fuse_getattr_count": 5038, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11761, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 557160557, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 37, + "fuse_op_forget_nanos": 432447, + "fuse_op_getattr_count": 5038, + "fuse_op_getattr_nanos": 63853236, + "fuse_op_lookup_count": 11761, + "fuse_op_lookup_nanos": 161440489, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 119093185, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26866715, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2261339, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4750, + "fuse_op_setattr_nanos": 1373845702, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 27901657, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 2, + "fuse_read_lane_wait_count": 16156, + "fuse_read_lane_wait_nanos": 3061810, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 32082, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10205, + "fuse_write_lane_wait_nanos": 1879246 + } + }, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 965994103, + "agentfs_batcher_commit_txns": 4814, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9398, + "attr_cache_hits": 26129, + "attr_cache_misses": 30795, + "base_fast_inode_invalidations": 15083, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 4, + "connection_reuse": 31863, + "connection_wait_count": 31867, + "connection_wait_nanos": 14580859, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4706, + "fuse_adapter_attr_misses": 5594, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27190, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 407930, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10300, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11811, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 558234652, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 551851, + "fuse_op_getattr_count": 10300, + "fuse_op_getattr_nanos": 82425253, + "fuse_op_lookup_count": 11811, + "fuse_op_lookup_nanos": 162059593, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 121664769, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 27888371, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3303336, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4764, + "fuse_op_setattr_nanos": 1377706298, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 28091038, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 16881, + "fuse_read_lane_wait_nanos": 3160850, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37453, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10247, + "fuse_write_lane_wait_nanos": 1965999, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20688, + "lookup_base_count": 24, + "lookup_count": 40454, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6873, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2307894 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15083, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 965994103, + "agentfs_batcher_commit_txns": 4814, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9398, + "attr_cache_hits": 26129, + "attr_cache_misses": 30795, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 4, + "connection_reuse": 31863, + "connection_wait_count": 31867, + "connection_wait_nanos": 14580859, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "getattr_count": 20688, + "lookup_base_count": 24, + "lookup_count": 40454, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6873, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2307894 + }, + "fuse": { + "fuse_adapter_attr_hits": 4706, + "fuse_adapter_attr_misses": 5594, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27190, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 407930, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10300, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11811, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 558234652, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 551851, + "fuse_op_getattr_count": 10300, + "fuse_op_getattr_nanos": 82425253, + "fuse_op_lookup_count": 11811, + "fuse_op_lookup_nanos": 162059593, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 121664769, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 27888371, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3303336, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4764, + "fuse_op_setattr_nanos": 1377706298, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 28091038, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 16881, + "fuse_read_lane_wait_nanos": 3160850, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 37453, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10247, + "fuse_write_lane_wait_nanos": 1965999 + } + }, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 971701743, + "agentfs_batcher_commit_txns": 4818, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26148, + "attr_cache_misses": 30828, + "base_fast_inode_invalidations": 15107, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33567, + "connection_wait_count": 33571, + "connection_wait_nanos": 14993388, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6098, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30283, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11698, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12103, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11698, + "fuse_op_getattr_nanos": 83960737, + "fuse_op_lookup_count": 12103, + "fuse_op_lookup_nanos": 166817254, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 124144058, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 29234237, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4770, + "fuse_op_setattr_nanos": 1388210878, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30567970, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17876, + "fuse_read_lane_wait_nanos": 3264437, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43316, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10261, + "fuse_write_lane_wait_nanos": 1969932, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20712, + "lookup_base_count": 24, + "lookup_count": 41052, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11408233 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15107, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 971701743, + "agentfs_batcher_commit_txns": 4818, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26148, + "attr_cache_misses": 30828, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33567, + "connection_wait_count": 33571, + "connection_wait_nanos": 14993388, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "getattr_count": 20712, + "lookup_base_count": 24, + "lookup_count": 41052, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11408233 + }, + "fuse": { + "fuse_adapter_attr_hits": 6098, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30283, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11698, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12103, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11698, + "fuse_op_getattr_nanos": 83960737, + "fuse_op_lookup_count": 12103, + "fuse_op_lookup_nanos": 166817254, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 124144058, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 29234237, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4770, + "fuse_op_setattr_nanos": 1388210878, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30567970, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17876, + "fuse_read_lane_wait_nanos": 3264437, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43316, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10261, + "fuse_write_lane_wait_nanos": 1969932 + } + }, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 971701743, + "agentfs_batcher_commit_txns": 4818, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26149, + "attr_cache_misses": 30828, + "base_fast_inode_invalidations": 15107, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33575, + "connection_wait_count": 33579, + "connection_wait_nanos": 14998739, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6167, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30359, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11767, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12104, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11767, + "fuse_op_getattr_nanos": 84048577, + "fuse_op_lookup_count": 12104, + "fuse_op_lookup_nanos": 166844152, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 124144058, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4770, + "fuse_op_setattr_nanos": 1388210878, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30567970, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17878, + "fuse_read_lane_wait_nanos": 3265404, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43392, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10261, + "fuse_write_lane_wait_nanos": 1969932, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20712, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11408233 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15107, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 971701743, + "agentfs_batcher_commit_txns": 4818, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26149, + "attr_cache_misses": 30828, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33575, + "connection_wait_count": 33579, + "connection_wait_nanos": 14998739, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20712, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11408233 + }, + "fuse": { + "fuse_adapter_attr_hits": 6167, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30359, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11767, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12104, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11767, + "fuse_op_getattr_nanos": 84048577, + "fuse_op_lookup_count": 12104, + "fuse_op_lookup_nanos": 166844152, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 124144058, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4770, + "fuse_op_setattr_nanos": 1388210878, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30567970, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17878, + "fuse_read_lane_wait_nanos": 3265404, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43392, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10261, + "fuse_write_lane_wait_nanos": 1969932 + } + }, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26149, + "attr_cache_misses": 30852, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33607, + "connection_wait_count": 33611, + "connection_wait_nanos": 15066290, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6183, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30383, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11783, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12104, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11783, + "fuse_op_getattr_nanos": 84122769, + "fuse_op_lookup_count": 12104, + "fuse_op_lookup_nanos": 166844152, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 126643306, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17886, + "fuse_read_lane_wait_nanos": 3278624, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43432, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20728, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 13387918 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26149, + "attr_cache_misses": 30852, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33607, + "connection_wait_count": 33611, + "connection_wait_nanos": 15066290, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20728, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 13387918 + }, + "fuse": { + "fuse_adapter_attr_hits": 6183, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30383, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11783, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12104, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11783, + "fuse_op_getattr_nanos": 84122769, + "fuse_op_lookup_count": 12104, + "fuse_op_lookup_nanos": 166844152, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 126643306, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17886, + "fuse_read_lane_wait_nanos": 3278624, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43432, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525 + } + }, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26149, + "attr_cache_misses": 30852, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33607, + "connection_wait_count": 33611, + "connection_wait_nanos": 15066290, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 8122, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5434, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32398, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13722, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12180, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13722, + "fuse_op_getattr_nanos": 86838317, + "fuse_op_lookup_count": 12180, + "fuse_op_lookup_nanos": 166942450, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 126651980, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17886, + "fuse_read_lane_wait_nanos": 3278624, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45459, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20728, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6954, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 13387918 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26149, + "attr_cache_misses": 30852, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33607, + "connection_wait_count": 33611, + "connection_wait_nanos": 15066290, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20728, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6954, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 13387918 + }, + "fuse": { + "fuse_adapter_attr_hits": 8122, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5434, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32398, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13722, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12180, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13722, + "fuse_op_getattr_nanos": 86838317, + "fuse_op_lookup_count": 12180, + "fuse_op_lookup_nanos": 166942450, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 126651980, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17886, + "fuse_read_lane_wait_nanos": 3278624, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45459, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525 + } + }, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26152, + "attr_cache_misses": 30854, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33631, + "connection_wait_count": 33635, + "connection_wait_nanos": 15081991, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8166, + "fuse_adapter_attr_misses": 5601, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5675, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32708, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13767, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12425, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13767, + "fuse_op_getattr_nanos": 86920998, + "fuse_op_lookup_count": 12425, + "fuse_op_lookup_nanos": 167302714, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 126665412, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 30939675, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 62880709, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3283354, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45805, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20730, + "lookup_base_count": 24, + "lookup_count": 41062, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7195, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 13730103 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26152, + "attr_cache_misses": 30854, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33631, + "connection_wait_count": 33635, + "connection_wait_nanos": 15081991, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "getattr_count": 20730, + "lookup_base_count": 24, + "lookup_count": 41062, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7195, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 13730103 + }, + "fuse": { + "fuse_adapter_attr_hits": 8166, + "fuse_adapter_attr_misses": 5601, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5675, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32708, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13767, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12425, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13767, + "fuse_op_getattr_nanos": 86920998, + "fuse_op_lookup_count": 12425, + "fuse_op_lookup_nanos": 167302714, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 126665412, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 30939675, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 62880709, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3283354, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45805, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525 + } + }, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26152, + "attr_cache_misses": 30854, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33634, + "connection_wait_count": 33638, + "connection_wait_nanos": 15088402, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8166, + "fuse_adapter_attr_misses": 5601, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5675, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32708, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13767, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12425, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13767, + "fuse_op_getattr_nanos": 86920998, + "fuse_op_lookup_count": 12425, + "fuse_op_lookup_nanos": 167302714, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 126665412, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 30939675, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 62880709, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3283354, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45805, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10271, + "fuse_write_lane_wait_nanos": 1978981, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20730, + "lookup_base_count": 24, + "lookup_count": 41062, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7195, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 13907880 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26152, + "attr_cache_misses": 30854, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33634, + "connection_wait_count": 33638, + "connection_wait_nanos": 15088402, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "getattr_count": 20730, + "lookup_base_count": 24, + "lookup_count": 41062, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7195, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 13907880 + }, + "fuse": { + "fuse_adapter_attr_hits": 8166, + "fuse_adapter_attr_misses": 5601, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5675, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32708, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13767, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12425, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13767, + "fuse_op_getattr_nanos": 86920998, + "fuse_op_lookup_count": 12425, + "fuse_op_lookup_nanos": 167302714, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 126665412, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 30939675, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 62880709, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3283354, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45805, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10271, + "fuse_write_lane_wait_nanos": 1978981 + } + }, + "source": "cli" + } + ], + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-f4f38b250e7f4cfc937dd9957456e3f9", + "--no-default-allows", + "--", + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/agentfs-base", + "duration_seconds": 4.789173212004243, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 962807731, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4732, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29060, + "base_fast_inode_invalidations": 15032, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 971, + "connection_create": 2, + "connection_reuse": 31236, + "connection_wait_count": 31238, + "connection_wait_nanos": 14138406, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 104, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5344, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21857, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 335791, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52591244, + "fuse_flush_count": 4732, + "fuse_flush_ranges": 4732, + "fuse_getattr_count": 5038, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11761, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 557160557, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 37, + "fuse_op_forget_nanos": 432447, + "fuse_op_getattr_count": 5038, + "fuse_op_getattr_nanos": 63853236, + "fuse_op_lookup_count": 11761, + "fuse_op_lookup_nanos": 161440489, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 119093185, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26866715, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2261339, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4750, + "fuse_op_setattr_nanos": 1373845702, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 27901657, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 2, + "fuse_read_lane_wait_count": 16156, + "fuse_read_lane_wait_nanos": 3061810, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32082, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10205, + "fuse_write_lane_wait_nanos": 1879246, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19340, + "lookup_base_count": 24, + "lookup_count": 40290, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6839, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1617016 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15032, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 962807731, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4732, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25921, + "attr_cache_misses": 29060, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 971, + "connection_create": 2, + "connection_reuse": 31236, + "connection_wait_count": 31238, + "connection_wait_nanos": 14138406, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "getattr_count": 19340, + "lookup_base_count": 24, + "lookup_count": 40290, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6839, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 18, + "wal_checkpoint_count": 10, + "wal_checkpoint_nanos": 1617016 + }, + "fuse": { + "fuse_adapter_attr_hits": 104, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5344, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21857, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 335791, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52591244, + "fuse_flush_count": 4732, + "fuse_flush_ranges": 4732, + "fuse_getattr_count": 5038, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11761, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 557160557, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 37, + "fuse_op_forget_nanos": 432447, + "fuse_op_getattr_count": 5038, + "fuse_op_getattr_nanos": 63853236, + "fuse_op_lookup_count": 11761, + "fuse_op_lookup_nanos": 161440489, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 783, + "fuse_op_other_nanos": 119093185, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26866715, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 2261339, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4750, + "fuse_op_setattr_nanos": 1373845702, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 27901657, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 2, + "fuse_read_lane_wait_count": 16156, + "fuse_read_lane_wait_nanos": 3061810, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 32082, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10205, + "fuse_write_lane_wait_nanos": 1879246 + } + }, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 965994103, + "agentfs_batcher_commit_txns": 4814, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9398, + "attr_cache_hits": 26129, + "attr_cache_misses": 30795, + "base_fast_inode_invalidations": 15083, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 4, + "connection_reuse": 31863, + "connection_wait_count": 31867, + "connection_wait_nanos": 14580859, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4706, + "fuse_adapter_attr_misses": 5594, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27190, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 407930, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10300, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11811, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 558234652, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 551851, + "fuse_op_getattr_count": 10300, + "fuse_op_getattr_nanos": 82425253, + "fuse_op_lookup_count": 11811, + "fuse_op_lookup_nanos": 162059593, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 121664769, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 27888371, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3303336, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4764, + "fuse_op_setattr_nanos": 1377706298, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 28091038, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 16881, + "fuse_read_lane_wait_nanos": 3160850, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37453, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10247, + "fuse_write_lane_wait_nanos": 1965999, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20688, + "lookup_base_count": 24, + "lookup_count": 40454, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6873, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2307894 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15083, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 965994103, + "agentfs_batcher_commit_txns": 4814, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9398, + "attr_cache_hits": 26129, + "attr_cache_misses": 30795, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create": 4, + "connection_reuse": 31863, + "connection_wait_count": 31867, + "connection_wait_nanos": 14580859, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "getattr_count": 20688, + "lookup_base_count": 24, + "lookup_count": 40454, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6873, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 22, + "wal_checkpoint_count": 12, + "wal_checkpoint_nanos": 2307894 + }, + "fuse": { + "fuse_adapter_attr_hits": 4706, + "fuse_adapter_attr_misses": 5594, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27190, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 407930, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10300, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 11811, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 558234652, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 551851, + "fuse_op_getattr_count": 10300, + "fuse_op_getattr_nanos": 82425253, + "fuse_op_lookup_count": 11811, + "fuse_op_lookup_nanos": 162059593, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 798, + "fuse_op_other_nanos": 121664769, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 27888371, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 3303336, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4764, + "fuse_op_setattr_nanos": 1377706298, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 28091038, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 16881, + "fuse_read_lane_wait_nanos": 3160850, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 37453, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10247, + "fuse_write_lane_wait_nanos": 1965999 + } + }, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 971701743, + "agentfs_batcher_commit_txns": 4818, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26148, + "attr_cache_misses": 30828, + "base_fast_inode_invalidations": 15107, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33567, + "connection_wait_count": 33571, + "connection_wait_nanos": 14993388, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6098, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30283, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11698, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12103, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11698, + "fuse_op_getattr_nanos": 83960737, + "fuse_op_lookup_count": 12103, + "fuse_op_lookup_nanos": 166817254, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 124144058, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 29234237, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4770, + "fuse_op_setattr_nanos": 1388210878, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30567970, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17876, + "fuse_read_lane_wait_nanos": 3264437, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43316, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10261, + "fuse_write_lane_wait_nanos": 1969932, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20712, + "lookup_base_count": 24, + "lookup_count": 41052, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11408233 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15107, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 971701743, + "agentfs_batcher_commit_txns": 4818, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26148, + "attr_cache_misses": 30828, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33567, + "connection_wait_count": 33571, + "connection_wait_nanos": 14993388, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "getattr_count": 20712, + "lookup_base_count": 24, + "lookup_count": 41052, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11408233 + }, + "fuse": { + "fuse_adapter_attr_hits": 6098, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30283, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11698, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12103, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11698, + "fuse_op_getattr_nanos": 83960737, + "fuse_op_lookup_count": 12103, + "fuse_op_lookup_nanos": 166817254, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 124144058, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 29234237, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4770, + "fuse_op_setattr_nanos": 1388210878, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30567970, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17876, + "fuse_read_lane_wait_nanos": 3264437, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43316, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10261, + "fuse_write_lane_wait_nanos": 1969932 + } + }, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 971701743, + "agentfs_batcher_commit_txns": 4818, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26149, + "attr_cache_misses": 30828, + "base_fast_inode_invalidations": 15107, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33575, + "connection_wait_count": 33579, + "connection_wait_nanos": 14998739, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6167, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30359, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11767, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12104, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11767, + "fuse_op_getattr_nanos": 84048577, + "fuse_op_lookup_count": 12104, + "fuse_op_lookup_nanos": 166844152, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 124144058, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4770, + "fuse_op_setattr_nanos": 1388210878, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30567970, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17878, + "fuse_read_lane_wait_nanos": 3265404, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43392, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10261, + "fuse_write_lane_wait_nanos": 1969932, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20712, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11408233 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15107, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 971701743, + "agentfs_batcher_commit_txns": 4818, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4689, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9402, + "attr_cache_hits": 26149, + "attr_cache_misses": 30828, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33575, + "connection_wait_count": 33579, + "connection_wait_nanos": 14998739, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20712, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 700, + "wal_checkpoint_nanos": 11408233 + }, + "fuse": { + "fuse_adapter_attr_hits": 6167, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30359, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11767, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12104, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11767, + "fuse_op_getattr_nanos": 84048577, + "fuse_op_lookup_count": 12104, + "fuse_op_lookup_nanos": 166844152, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3560, + "fuse_op_other_nanos": 124144058, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4770, + "fuse_op_setattr_nanos": 1388210878, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 30567970, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17878, + "fuse_read_lane_wait_nanos": 3265404, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43392, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10261, + "fuse_write_lane_wait_nanos": 1969932 + } + }, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26149, + "attr_cache_misses": 30852, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33607, + "connection_wait_count": 33611, + "connection_wait_nanos": 15066290, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6183, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30383, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11783, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12104, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11783, + "fuse_op_getattr_nanos": 84122769, + "fuse_op_lookup_count": 12104, + "fuse_op_lookup_nanos": 166844152, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 126643306, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17886, + "fuse_read_lane_wait_nanos": 3278624, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43432, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20728, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 13387918 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26149, + "attr_cache_misses": 30852, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33607, + "connection_wait_count": 33611, + "connection_wait_nanos": 15066290, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20728, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6878, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 13387918 + }, + "fuse": { + "fuse_adapter_attr_hits": 6183, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5358, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30383, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11783, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12104, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 11783, + "fuse_op_getattr_nanos": 84122769, + "fuse_op_lookup_count": 12104, + "fuse_op_lookup_nanos": 166844152, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3568, + "fuse_op_other_nanos": 126643306, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17886, + "fuse_read_lane_wait_nanos": 3278624, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 43432, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525 + } + }, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26149, + "attr_cache_misses": 30852, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33607, + "connection_wait_count": 33611, + "connection_wait_nanos": 15066290, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 8122, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5434, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32398, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13722, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12180, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13722, + "fuse_op_getattr_nanos": 86838317, + "fuse_op_lookup_count": 12180, + "fuse_op_lookup_nanos": 166942450, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 126651980, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17886, + "fuse_read_lane_wait_nanos": 3278624, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45459, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20728, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6954, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 13387918 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26149, + "attr_cache_misses": 30852, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33607, + "connection_wait_count": 33611, + "connection_wait_nanos": 15066290, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "getattr_count": 20728, + "lookup_base_count": 24, + "lookup_count": 41054, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6954, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 1398, + "wal_checkpoint_count": 708, + "wal_checkpoint_nanos": 13387918 + }, + "fuse": { + "fuse_adapter_attr_hits": 8122, + "fuse_adapter_attr_misses": 5600, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5434, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32398, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13722, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12180, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13722, + "fuse_op_getattr_nanos": 86838317, + "fuse_op_lookup_count": 12180, + "fuse_op_lookup_nanos": 166942450, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3580, + "fuse_op_other_nanos": 126651980, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30620507, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 61766205, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17886, + "fuse_read_lane_wait_nanos": 3278624, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45459, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525 + } + }, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26152, + "attr_cache_misses": 30854, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33631, + "connection_wait_count": 33635, + "connection_wait_nanos": 15081991, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8166, + "fuse_adapter_attr_misses": 5601, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5675, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32708, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13767, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12425, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13767, + "fuse_op_getattr_nanos": 86920998, + "fuse_op_lookup_count": 12425, + "fuse_op_lookup_nanos": 167302714, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 126665412, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 30939675, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 62880709, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3283354, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45805, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20730, + "lookup_base_count": 24, + "lookup_count": 41062, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7195, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 13730103 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26152, + "attr_cache_misses": 30854, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33631, + "connection_wait_count": 33635, + "connection_wait_nanos": 15081991, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "getattr_count": 20730, + "lookup_base_count": 24, + "lookup_count": 41062, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7195, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 717, + "wal_checkpoint_nanos": 13730103 + }, + "fuse": { + "fuse_adapter_attr_hits": 8166, + "fuse_adapter_attr_misses": 5601, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5675, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32708, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13767, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12425, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13767, + "fuse_op_getattr_nanos": 86920998, + "fuse_op_lookup_count": 12425, + "fuse_op_lookup_nanos": 167302714, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 126665412, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 30939675, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 62880709, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3283354, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45805, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1977525 + } + }, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26152, + "attr_cache_misses": 30854, + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33634, + "connection_wait_count": 33638, + "connection_wait_nanos": 15088402, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8166, + "fuse_adapter_attr_misses": 5601, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5675, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32708, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13767, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12425, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13767, + "fuse_op_getattr_nanos": 86920998, + "fuse_op_lookup_count": 12425, + "fuse_op_lookup_nanos": 167302714, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 126665412, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 30939675, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 62880709, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3283354, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45805, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10271, + "fuse_write_lane_wait_nanos": 1978981, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20730, + "lookup_base_count": 24, + "lookup_count": 41062, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7195, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 13907880 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 15123, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 1, + "fuse_readdirplus_mode": 2, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_workers_configured": 7, + "fuse_writeback_cache_enabled": 1 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 43, + "agentfs_batcher_commit_latency_ns_total": 975959459, + "agentfs_batcher_commit_txns": 4826, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4697, + "agentfs_batcher_drains_timer": 14, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9410, + "attr_cache_hits": 26152, + "attr_cache_misses": 30854, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1002, + "connection_create": 4, + "connection_reuse": 33634, + "connection_wait_count": 33638, + "connection_wait_nanos": 15088402, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "getattr_count": 20730, + "lookup_base_count": 24, + "lookup_count": 41062, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7195, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 1416, + "wal_checkpoint_count": 719, + "wal_checkpoint_nanos": 13907880 + }, + "fuse": { + "fuse_adapter_attr_hits": 8166, + "fuse_adapter_attr_misses": 5601, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5675, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32708, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 51, + "fuse_dispatch_wait_count": 51, + "fuse_dispatch_wait_nanos": 444898, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13767, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 12425, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 559055187, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 3422, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 604758, + "fuse_op_getattr_count": 13767, + "fuse_op_getattr_nanos": 86920998, + "fuse_op_lookup_count": 12425, + "fuse_op_lookup_nanos": 167302714, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 962, + "fuse_op_other_count": 3616, + "fuse_op_other_nanos": 126665412, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 30939675, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 62880709, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 4778, + "fuse_op_setattr_nanos": 1396541613, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 32304104, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3283354, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 45805, + "fuse_worker_queue_depth_peak": 2, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10271, + "fuse_write_lane_wait_nanos": 1978981 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 64602, + "stderr_tail": "en_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":43,\"agentfs_batcher_commit_latency_ns_total\":975959459,\"agentfs_batcher_commit_txns\":4826,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":14,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9410,\"attr_cache_hits\":26149,\"attr_cache_misses\":30852,\"chunk_read_chunks\":267,\"chunk_read_queries\":122,\"chunk_write_chunks\":1002,\"connection_create\":4,\"connection_reuse\":33607,\"connection_wait_count\":33611,\"connection_wait_nanos\":15066290,\"dentry_cache_hits\":26125,\"dentry_cache_misses\":14052,\"getattr_count\":20728,\"lookup_base_count\":24,\"lookup_count\":41054,\"lookup_delta_count\":7547,\"lookup_whiteout_count\":0,\"negative_cache_hits\":6954,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46085,\"negative_lookup_count\":14922,\"path_cache_hits\":26125,\"path_cache_misses\":14052,\"path_component_count\":37632,\"path_resolution_count\":8140,\"readdir_count\":1,\"readdir_plus_count\":1398,\"wal_checkpoint_count\":708,\"wal_checkpoint_nanos\":13387918},\"fuse\":{\"fuse_adapter_attr_hits\":8122,\"fuse_adapter_attr_misses\":5600,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6734,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5434,\"fuse_adapter_negative_misses\":6734,\"fuse_callback_count\":32398,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":6,\"fuse_dispatch_parallel_tasks\":51,\"fuse_dispatch_wait_count\":51,\"fuse_dispatch_wait_nanos\":444898,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13722,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12180,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":559055187,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":3422,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":604758,\"fuse_op_getattr_count\":13722,\"fuse_op_getattr_nanos\":86838317,\"fuse_op_lookup_count\":12180,\"fuse_op_lookup_nanos\":166942450,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":962,\"fuse_op_other_count\":3580,\"fuse_op_other_nanos\":126651980,\"fuse_op_read_count\":239,\"fuse_op_read_nanos\":30620507,\"fuse_op_readdirplus_count\":1407,\"fuse_op_readdirplus_nanos\":61766205,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4778,\"fuse_op_setattr_nanos\":1396541613,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":32304104,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":239,\"fuse_read_lane_max_concurrent\":4,\"fuse_read_lane_wait_count\":17886,\"fuse_read_lane_wait_nanos\":3278624,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1407,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45459,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1977525}},\"source\":\"phase-checkpoint-6\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":43,\"agentfs_batcher_commit_latency_ns_total\":975959459,\"agentfs_batcher_commit_txns\":4826,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":14,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9410,\"attr_cache_hits\":26152,\"attr_cache_misses\":30854,\"base_fast_inode_invalidations\":15123,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1002,\"connection_create\":4,\"connection_reuse\":33631,\"connection_wait_count\":33635,\"connection_wait_nanos\":15081991,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":8166,\"fuse_adapter_attr_misses\":5601,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5675,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32708,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":6,\"fuse_dispatch_parallel_tasks\":51,\"fuse_dispatch_wait_count\":51,\"fuse_dispatch_wait_nanos\":444898,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13767,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12425,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":559055187,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":3422,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":604758,\"fuse_op_getattr_count\":13767,\"fuse_op_getattr_nanos\":86920998,\"fuse_op_lookup_count\":12425,\"fuse_op_lookup_nanos\":167302714,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":962,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":126665412,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":30939675,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":62880709,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4778,\"fuse_op_setattr_nanos\":1396541613,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":32304104,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":4,\"fuse_read_lane_wait_count\":17901,\"fuse_read_lane_wait_nanos\":3283354,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45805,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1977525,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20730,\"lookup_base_count\":24,\"lookup_count\":41062,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7195,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14924,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":717,\"wal_checkpoint_nanos\":13730103},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":15123,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":43,\"agentfs_batcher_commit_latency_ns_total\":975959459,\"agentfs_batcher_commit_txns\":4826,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":14,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9410,\"attr_cache_hits\":26152,\"attr_cache_misses\":30854,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1002,\"connection_create\":4,\"connection_reuse\":33631,\"connection_wait_count\":33635,\"connection_wait_nanos\":15081991,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"getattr_count\":20730,\"lookup_base_count\":24,\"lookup_count\":41062,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7195,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14924,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":717,\"wal_checkpoint_nanos\":13730103},\"fuse\":{\"fuse_adapter_attr_hits\":8166,\"fuse_adapter_attr_misses\":5601,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5675,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32708,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":6,\"fuse_dispatch_parallel_tasks\":51,\"fuse_dispatch_wait_count\":51,\"fuse_dispatch_wait_nanos\":444898,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13767,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12425,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":559055187,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":3422,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":604758,\"fuse_op_getattr_count\":13767,\"fuse_op_getattr_nanos\":86920998,\"fuse_op_lookup_count\":12425,\"fuse_op_lookup_nanos\":167302714,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":962,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":126665412,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":30939675,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":62880709,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4778,\"fuse_op_setattr_nanos\":1396541613,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":32304104,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":4,\"fuse_read_lane_wait_count\":17901,\"fuse_read_lane_wait_nanos\":3283354,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45805,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1977525}},\"source\":\"phase-checkpoint-7\"}\n\nSession: git-workload-f4f38b250e7f4cfc937dd9957456e3f9\n\nTo resume this session:\n agentfs run --session git-workload-f4f38b250e7f4cfc937dd9957456e3f9\n\nTo see what changed:\n agentfs diff git-workload-f4f38b250e7f4cfc937dd9957456e3f9\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":43,\"agentfs_batcher_commit_latency_ns_total\":975959459,\"agentfs_batcher_commit_txns\":4826,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":14,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9410,\"attr_cache_hits\":26152,\"attr_cache_misses\":30854,\"base_fast_inode_invalidations\":15123,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1002,\"connection_create\":4,\"connection_reuse\":33634,\"connection_wait_count\":33638,\"connection_wait_nanos\":15088402,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":8166,\"fuse_adapter_attr_misses\":5601,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5675,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32708,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":6,\"fuse_dispatch_parallel_tasks\":51,\"fuse_dispatch_wait_count\":51,\"fuse_dispatch_wait_nanos\":444898,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13767,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12425,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":559055187,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":3422,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":604758,\"fuse_op_getattr_count\":13767,\"fuse_op_getattr_nanos\":86920998,\"fuse_op_lookup_count\":12425,\"fuse_op_lookup_nanos\":167302714,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":962,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":126665412,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":30939675,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":62880709,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4778,\"fuse_op_setattr_nanos\":1396541613,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":32304104,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":4,\"fuse_read_lane_wait_count\":17901,\"fuse_read_lane_wait_nanos\":3283354,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45805,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10271,\"fuse_write_lane_wait_nanos\":1978981,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20730,\"lookup_base_count\":24,\"lookup_count\":41062,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7195,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14924,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":719,\"wal_checkpoint_nanos\":13907880},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":15123,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":1,\"fuse_readdirplus_mode\":2,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_workers_configured\":7,\"fuse_writeback_cache_enabled\":1},\"core\":{\"agentfs_batcher_coalesced_ranges\":43,\"agentfs_batcher_commit_latency_ns_total\":975959459,\"agentfs_batcher_commit_txns\":4826,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4697,\"agentfs_batcher_drains_timer\":14,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":3,\"agentfs_batcher_txn_inodes_total\":9410,\"attr_cache_hits\":26152,\"attr_cache_misses\":30854,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1002,\"connection_create\":4,\"connection_reuse\":33634,\"connection_wait_count\":33638,\"connection_wait_nanos\":15088402,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"getattr_count\":20730,\"lookup_base_count\":24,\"lookup_count\":41062,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7195,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14924,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":1416,\"wal_checkpoint_count\":719,\"wal_checkpoint_nanos\":13907880},\"fuse\":{\"fuse_adapter_attr_hits\":8166,\"fuse_adapter_attr_misses\":5601,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5675,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32708,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":6,\"fuse_dispatch_parallel_tasks\":51,\"fuse_dispatch_wait_count\":51,\"fuse_dispatch_wait_nanos\":444898,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13767,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":12425,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":559055187,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":3422,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":604758,\"fuse_op_getattr_count\":13767,\"fuse_op_getattr_nanos\":86920998,\"fuse_op_lookup_count\":12425,\"fuse_op_lookup_nanos\":167302714,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":962,\"fuse_op_other_count\":3616,\"fuse_op_other_nanos\":126665412,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":30939675,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":62880709,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":4778,\"fuse_op_setattr_nanos\":1396541613,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":32304104,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":4,\"fuse_read_lane_wait_count\":17901,\"fuse_read_lane_wait_nanos\":3283354,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":45805,\"fuse_worker_queue_depth_peak\":2,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10271,\"fuse_write_lane_wait_nanos\":1978981}},\"source\":\"cli\"}\n", + "stdout_bytes": 19040, + "stdout_tail": "2026-07-04T01:13:30.872785Z INFO agentfs::fuser::session: resolved FUSE dispatch mode: parallel workers=7 queue_capacity=28\n2026-07-04T01:13:30.873041Z INFO agentfs::fuse: advertising FUSE_OVER_IO_URING\n2026-07-04T01:13:30.873087Z INFO agentfs::fuser::uring: starting fuse-over-io_uring queues nr_queues=14 depth=4\n2026-07-04T01:13:32.253064Z WARN agentfs::fuser::request: Request RequestId(21391): Failed to send reply: No such file or directory (os error 2)\n{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.07817813300061971, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work\", \"duration_seconds\": 0.025902216002577916, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work\", \"duration_seconds\": 0.02555491500243079, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work\", \"duration_seconds\": 0.02663081900391262, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.018203587998868898, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work\", \"duration_seconds\": 0.20207263898919336, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work\", \"duration_seconds\": 0.06031042900576722, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/mirror.git\", \"/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/agentfs-base\", \"duration_seconds\": 3.425520018005045, \"returncode\": 0, \"stderr_bytes\": 2604, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\nUpdating files: 30% (1419/4644)\\nUpdating files: 31% (1440/4644)\\nUpdating files: 32% (1487/4644)\\nUpdating files: 33% (1533/4644)\\nUpdating files: 34% (1579/4644)\\nUpdating files: 35% (1626/4644)\\nUpdating files: 36% (1672/4644)\\nUpdating files: 37% (1719/4644)\\nUpdating files: 38% (1765/4644)\\nUpdating files: 39% (1812/4644)\\nUpdating files: 40% (1858/4644)\\nUpdating files: 41% (1905/4644)\\nUpdating files: 42% (1951/4644)\\nUpdating files: 43% (1997/4644)\\nUpdating files: 44% (2044/4644)\\nUpdating files: 45% (2090/4644)\\nUpdating files: 46% (2137/4644)\\nUpdating files: 47% (2183/4644)\\nUpdating files: 48% (2230/4644)\\nUpdating files: 49% (2276/4644)\\nUpdating files: 50% (2322/4644)\\nUpdating files: 51% (2369/4644)\\nUpdating files: 52% (2415/4644)\\nUpdating files: 53% (2462/4644)\\nUpdating files: 54% (2508/4644)\\nUpdating files: 55% (2555/4644)\\nUpdating files: 56% (2601/4644)\\nUpdating files: 57% (2648/4644)\\nUpdating files: 58% (2694/4644)\\nUpdating files: 59% (2740/4644)\\nUpdating files: 60% (2787/4644)\\nUpdating files: 60% (2822/4644)\\nUpdating files: 61% (2833/4644)\\nUpdating files: 62% (2880/4644)\\nUpdating files: 63% (2926/4644)\\nUpdating files: 64% (2973/4644)\\nUpdating files: 65% (3019/4644)\\nUpdating files: 66% (3066/4644)\\nUpdating files: 67% (3112/4644)\\nUpdating files: 68% (3158/4644)\\nUpdating files: 69% (3205/4644)\\nUpdating files: 70% (3251/4644)\\nUpdating files: 71% (3298/4644)\\nUpdating files: 72% (3344/4644)\\nUpdating files: 73% (3391/4644)\\nUpdating files: 74% (3437/4644)\\nUpdating files: 75% (3483/4644)\\nUpdating files: 76% (3530/4644)\\nUpdating files: 77% (3576/4644)\\nUpdating files: 78% (3623/4644)\\nUpdating files: 79% (3669/4644)\\nUpdating files: 80% (3716/4644)\\nUpdating files: 81% (3762/4644)\\nUpdating files: 82% (3809/4644)\\nUpdating files: 83% (3855/4644)\\nUpdating files: 84% (3901/4644)\\nUpdating files: 85% (3948/4644)\\nUpdating files: 86% (3994/4644)\\nUpdating files: 87% (4041/4644)\\nUpdating files: 88% (4087/4644)\\nUpdating files: 89% (4134/4644)\\nUpdating files: 90% (4180/4644)\\nUpdating files: 91% (4227/4644)\\nUpdating files: 92% (4273/4644)\\nUpdating files: 93% (4319/4644)\\nUpdating files: 94% (4366/4644)\\nUpdating files: 95% (4412/4644)\\nUpdating files: 96% (4459/4644)\\nUpdating files: 96% (4463/4644)\\nUpdating files: 97% (4505/4644)\\nUpdating files: 98% (4552/4644)\\nUpdating files: 99% (4598/4644)\\nUpdating files: 100% (4644/4644)\\nUpdating files: 100% (4644/4644), done.\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work\", \"duration_seconds\": 0.04579617400304414, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work\", \"duration_seconds\": 0.1276607129984768, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.06298040700494312, \"clone\": 3.4255687719996786, \"diff\": 0.07817813300061971, \"edit\": 0.018203587998868898, \"fsck\": 0.20209814199188258, \"read_search\": 0.010043239002698101, \"status\": 0.1734860779979499}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work\", \"duration_seconds\": 0.005569957007537596, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 4.671860290996847}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.07817813300061971, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work", + "duration_seconds": 0.025902216002577916, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work", + "duration_seconds": 0.02555491500243079, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work", + "duration_seconds": 0.02663081900391262, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.018203587998868898, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work", + "duration_seconds": 0.20207263898919336, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work", + "duration_seconds": 0.06031042900576722, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/mirror.git", + "/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/agentfs-base", + "duration_seconds": 3.425520018005045, + "returncode": 0, + "stderr_bytes": 2604, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\nUpdating files: 30% (1419/4644)\nUpdating files: 31% (1440/4644)\nUpdating files: 32% (1487/4644)\nUpdating files: 33% (1533/4644)\nUpdating files: 34% (1579/4644)\nUpdating files: 35% (1626/4644)\nUpdating files: 36% (1672/4644)\nUpdating files: 37% (1719/4644)\nUpdating files: 38% (1765/4644)\nUpdating files: 39% (1812/4644)\nUpdating files: 40% (1858/4644)\nUpdating files: 41% (1905/4644)\nUpdating files: 42% (1951/4644)\nUpdating files: 43% (1997/4644)\nUpdating files: 44% (2044/4644)\nUpdating files: 45% (2090/4644)\nUpdating files: 46% (2137/4644)\nUpdating files: 47% (2183/4644)\nUpdating files: 48% (2230/4644)\nUpdating files: 49% (2276/4644)\nUpdating files: 50% (2322/4644)\nUpdating files: 51% (2369/4644)\nUpdating files: 52% (2415/4644)\nUpdating files: 53% (2462/4644)\nUpdating files: 54% (2508/4644)\nUpdating files: 55% (2555/4644)\nUpdating files: 56% (2601/4644)\nUpdating files: 57% (2648/4644)\nUpdating files: 58% (2694/4644)\nUpdating files: 59% (2740/4644)\nUpdating files: 60% (2787/4644)\nUpdating files: 60% (2822/4644)\nUpdating files: 61% (2833/4644)\nUpdating files: 62% (2880/4644)\nUpdating files: 63% (2926/4644)\nUpdating files: 64% (2973/4644)\nUpdating files: 65% (3019/4644)\nUpdating files: 66% (3066/4644)\nUpdating files: 67% (3112/4644)\nUpdating files: 68% (3158/4644)\nUpdating files: 69% (3205/4644)\nUpdating files: 70% (3251/4644)\nUpdating files: 71% (3298/4644)\nUpdating files: 72% (3344/4644)\nUpdating files: 73% (3391/4644)\nUpdating files: 74% (3437/4644)\nUpdating files: 75% (3483/4644)\nUpdating files: 76% (3530/4644)\nUpdating files: 77% (3576/4644)\nUpdating files: 78% (3623/4644)\nUpdating files: 79% (3669/4644)\nUpdating files: 80% (3716/4644)\nUpdating files: 81% (3762/4644)\nUpdating files: 82% (3809/4644)\nUpdating files: 83% (3855/4644)\nUpdating files: 84% (3901/4644)\nUpdating files: 85% (3948/4644)\nUpdating files: 86% (3994/4644)\nUpdating files: 87% (4041/4644)\nUpdating files: 88% (4087/4644)\nUpdating files: 89% (4134/4644)\nUpdating files: 90% (4180/4644)\nUpdating files: 91% (4227/4644)\nUpdating files: 92% (4273/4644)\nUpdating files: 93% (4319/4644)\nUpdating files: 94% (4366/4644)\nUpdating files: 95% (4412/4644)\nUpdating files: 96% (4459/4644)\nUpdating files: 96% (4463/4644)\nUpdating files: 97% (4505/4644)\nUpdating files: 98% (4552/4644)\nUpdating files: 99% (4598/4644)\nUpdating files: 100% (4644/4644)\nUpdating files: 100% (4644/4644), done.\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work", + "duration_seconds": 0.04579617400304414, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work", + "duration_seconds": 0.1276607129984768, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.06298040700494312, + "clone": 3.4255687719996786, + "diff": 0.07817813300061971, + "edit": 0.018203587998868898, + "fsck": 0.20209814199188258, + "read_search": 0.010043239002698101, + "status": 0.1734860779979499 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/agentfs-base/work", + "duration_seconds": 0.005569957007537596, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 4.671860290996847 + } + }, + "base_tree": { + "after": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "c254f4bb1095d9df465c5cf4c9a599abab780d2333f6e5cdea71a34945200079", + "symlinks": 0 + }, + "before": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "c254f4bb1095d9df465c5cf4c9a599abab780d2333f6e5cdea71a34945200079", + "symlinks": 0 + }, + "unchanged": true + }, + "benchmark": "phase7-git-workload", + "command": { + "agentfs_prefix": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-f4f38b250e7f4cfc937dd9957456e3f9", + "--no-default-allows", + "--" + ], + "argv": [ + "/home/ain3sh/factory/vfs/scripts/validation/git-workload-benchmark.py", + "--output", + "/tmp/vfs-val/misc-a-bench-rebaseline/run3.json.iterations/warmup-00.json", + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "workload_argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ] + }, + "correctness": { + "agentfs_backup_verify": true, + "agentfs_base_unchanged": true, + "agentfs_db_inspectable": true, + "agentfs_integrity_require_portable": true, + "agentfs_no_nonempty_sidecars": true, + "agentfs_portable": true, + "agentfs_returncode_zero": true, + "equivalence": { + "agentfs": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + }, + "checked": true, + "equivalent": true, + "native": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + } + }, + "native_returncode_zero": true, + "passed": true, + "performance_passed": false + }, + "database": { + "after": { + "artifacts": [ + { + "bytes": 57151488, + "path": "/tmp/agentfs-git-workload-s58r0ykm/home/.agentfs/run/git-workload-f4f38b250e7f4cfc937dd9957456e3f9/delta.db" + } + ], + "path": "/tmp/agentfs-git-workload-s58r0ykm/home/.agentfs/run/git-workload-f4f38b250e7f4cfc937dd9957456e3f9/delta.db", + "total_bytes": 57151488 + }, + "backup": { + "artifacts": { + "artifacts": [ + { + "bytes": 57151488, + "path": "/tmp/agentfs-git-workload-s58r0ykm/git-workload-backup.db" + } + ], + "path": "/tmp/agentfs-git-workload-s58r0ykm/git-workload-backup.db", + "total_bytes": 57151488 + }, + "inspect": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "path": "/tmp/agentfs-git-workload-s58r0ykm/git-workload-backup.db", + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "backup", + "/tmp/agentfs-git-workload-s58r0ykm/home/.agentfs/run/git-workload-f4f38b250e7f4cfc937dd9957456e3f9/delta.db", + "/tmp/agentfs-git-workload-s58r0ykm/git-workload-backup.db", + "--verify" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm", + "duration_seconds": 2.736975945997983, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 0, + "fuse_readdirplus_mode": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_workers_configured": 0, + "fuse_writeback_cache_enabled": 0 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "fuse": { + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 7355, + "stderr_tail": "{\"counters\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_mode\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_workers_configured\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0,\"fuse_writeback_cache_enabled\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":0,\"fuse_readdirplus_mode\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_workers_configured\":0,\"fuse_writeback_cache_enabled\":0},\"core\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"fuse\":{\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0}},\"source\":\"cli\"}\n", + "stdout_bytes": 241, + "stdout_tail": "Source: /tmp/agentfs-git-workload-s58r0ykm/home/.agentfs/run/git-workload-f4f38b250e7f4cfc937dd9957456e3f9/delta.db\nBackup: /tmp/agentfs-git-workload-s58r0ykm/git-workload-backup.db\nCheckpoint: complete\nCopy: complete\nVerification: complete\n", + "timed_out": false + } + }, + "inspect_after": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "integrity": { + "result": { + "checks": [ + { + "detail": "ok", + "name": "pragma.integrity_check", + "ok": true, + "violating_rows": null + }, + { + "detail": "present", + "name": "schema.table.fs_config", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_symlink", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.kv_store", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.tool_calls", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 0.5", + "name": "config.schema_version", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 65536", + "name": "config.chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 16384", + "name": "config.inline_threshold", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.kind_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_has_no_chunks", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunked_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_size_matches_blob", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.non_regular_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_reference_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunk_length_within_chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 1, expected 1", + "name": "namespace.root_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_is_directory", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_target_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_root_inode_has_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_names_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_directory_nlink_matches_dentries", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.directory_nlink_positive", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.rows_reference_symlink_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.inodes_have_rows", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable: no partial-origin rows", + "name": "overlay.portability_status", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable requirement satisfied", + "name": "overlay.require_portable", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_regular", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_sizes_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_paths_absolute", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_references_partial_origin", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_unique", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_index_in_range", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.whiteout_paths_absolute", + "ok": true, + "violating_rows": 0 + } + ], + "database": "/tmp/agentfs-git-workload-s58r0ykm/home/.agentfs/run/git-workload-f4f38b250e7f4cfc937dd9957456e3f9/delta.db", + "ok": true, + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true + }, + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "integrity", + "/tmp/agentfs-git-workload-s58r0ykm/home/.agentfs/run/git-workload-f4f38b250e7f4cfc937dd9957456e3f9/delta.db", + "--json", + "--require-portable" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm", + "duration_seconds": 2.803227793003316, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "sections": { + "base": { + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0 + }, + "config": { + "fuse_keepcache_enabled": 0, + "fuse_readdirplus_mode": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_workers_configured": 0, + "fuse_writeback_cache_enabled": 0 + }, + "core": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create": 0, + "connection_reuse": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "fuse": { + "fuse_adapter_attr_hits": 0, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 0, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 0, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 0, + "fuse_op_getattr_nanos": 0, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_release_count": 0, + "fuse_op_release_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_uring_requests": 0, + "fuse_worker_queue_depth_peak": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0 + } + }, + "source": "cli" + } + ], + "returncode": 0, + "stderr_bytes": 7355, + "stderr_tail": "{\"counters\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_mode\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_workers_configured\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0,\"fuse_writeback_cache_enabled\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"sections\":{\"base\":{\"base_fast_inode_invalidations\":0,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0},\"config\":{\"fuse_keepcache_enabled\":0,\"fuse_readdirplus_mode\":0,\"fuse_ttl_attr_ms\":0,\"fuse_ttl_entry_ms\":0,\"fuse_ttl_neg_ms\":0,\"fuse_workers_configured\":0,\"fuse_writeback_cache_enabled\":0},\"core\":{\"agentfs_batcher_coalesced_ranges\":0,\"agentfs_batcher_commit_latency_ns_total\":0,\"agentfs_batcher_commit_txns\":0,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":0,\"agentfs_batcher_drains_timer\":0,\"agentfs_batcher_enqueues\":0,\"agentfs_batcher_pending_max_bytes\":0,\"agentfs_batcher_txn_inodes_max\":0,\"agentfs_batcher_txn_inodes_total\":0,\"attr_cache_hits\":0,\"attr_cache_misses\":0,\"chunk_read_chunks\":0,\"chunk_read_queries\":0,\"chunk_write_chunks\":0,\"connection_create\":0,\"connection_reuse\":0,\"connection_wait_count\":0,\"connection_wait_nanos\":0,\"dentry_cache_hits\":0,\"dentry_cache_misses\":0,\"getattr_count\":0,\"lookup_base_count\":0,\"lookup_count\":0,\"lookup_delta_count\":0,\"lookup_whiteout_count\":0,\"negative_cache_hits\":0,\"negative_cache_invalidations\":0,\"negative_cache_misses\":0,\"negative_lookup_count\":0,\"path_cache_hits\":0,\"path_cache_misses\":0,\"path_component_count\":0,\"path_resolution_count\":0,\"readdir_count\":0,\"readdir_plus_count\":0,\"wal_checkpoint_count\":0,\"wal_checkpoint_nanos\":0},\"fuse\":{\"fuse_adapter_attr_hits\":0,\"fuse_adapter_attr_misses\":0,\"fuse_adapter_entry_hits\":0,\"fuse_adapter_entry_misses\":0,\"fuse_adapter_inval_entry_notifications\":0,\"fuse_adapter_inval_inode_notifications\":0,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":0,\"fuse_adapter_negative_misses\":0,\"fuse_callback_count\":0,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":0,\"fuse_dispatch_parallel_tasks\":0,\"fuse_dispatch_wait_count\":0,\"fuse_dispatch_wait_nanos\":0,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":0,\"fuse_flush_count\":0,\"fuse_flush_ranges\":0,\"fuse_getattr_count\":0,\"fuse_ino_file_resolutions\":0,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_lookup_count\":0,\"fuse_noflush_enosys_replies\":0,\"fuse_noopen_enosys_replies\":0,\"fuse_op_create_count\":0,\"fuse_op_create_nanos\":0,\"fuse_op_flush_count\":0,\"fuse_op_flush_nanos\":0,\"fuse_op_forget_count\":0,\"fuse_op_forget_nanos\":0,\"fuse_op_getattr_count\":0,\"fuse_op_getattr_nanos\":0,\"fuse_op_lookup_count\":0,\"fuse_op_lookup_nanos\":0,\"fuse_op_open_count\":0,\"fuse_op_open_nanos\":0,\"fuse_op_other_count\":0,\"fuse_op_other_nanos\":0,\"fuse_op_read_count\":0,\"fuse_op_read_nanos\":0,\"fuse_op_readdirplus_count\":0,\"fuse_op_readdirplus_nanos\":0,\"fuse_op_release_count\":0,\"fuse_op_release_nanos\":0,\"fuse_op_setattr_count\":0,\"fuse_op_setattr_nanos\":0,\"fuse_op_write_count\":0,\"fuse_op_write_nanos\":0,\"fuse_open_count\":0,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":0,\"fuse_read_lane_max_concurrent\":0,\"fuse_read_lane_wait_count\":0,\"fuse_read_lane_wait_nanos\":0,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":0,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":0,\"fuse_readdirplus_do_requested\":0,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_uring_requests\":0,\"fuse_worker_queue_depth_peak\":0,\"fuse_write_bytes\":0,\"fuse_write_count\":0,\"fuse_write_lane_wait_count\":0,\"fuse_write_lane_wait_nanos\":0}},\"source\":\"cli\"}\n", + "stdout_bytes": 6395, + "stdout_tail": "{\n \"database\": \"/tmp/agentfs-git-workload-s58r0ykm/home/.agentfs/run/git-workload-f4f38b250e7f4cfc937dd9957456e3f9/delta.db\",\n \"ok\": true,\n \"portable\": true,\n \"origin_backed\": false,\n \"partial_origin_rows\": 0,\n \"checks\": [\n {\n \"name\": \"pragma.integrity_check\",\n \"ok\": true,\n \"detail\": \"ok\",\n \"violating_rows\": null\n },\n {\n \"name\": \"schema.table.fs_config\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_inode\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_dentry\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_data\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_symlink\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.kv_store\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.tool_calls\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.schema_version\",\n \"ok\": true,\n \"detail\": \"found 0.5\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.chunk_size\",\n \"ok\": true,\n \"detail\": \"found 65536\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.inline_threshold\",\n \"ok\": true,\n \"detail\": \"found 16384\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.kind_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_has_no_chunks\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunked_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_size_matches_blob\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.non_regular_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_reference_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunk_length_within_chunk_size\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.root_inode\",\n \"ok\": true,\n \"detail\": \"found 1, expected 1\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_is_directory\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_target_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_root_inode_has_dentry\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_names_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_directory_nlink_matches_dentries\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.directory_nlink_positive\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.rows_reference_symlink_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.inodes_have_rows\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.portability_status\",\n \"ok\": true,\n \"detail\": \"portable: no partial-origin rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.require_portable\",\n \"ok\": true,\n \"detail\": \"portable requirement satisfied\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_regular\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_sizes_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_references_partial_origin\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_unique\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_index_in_range\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.whiteout_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n }\n ]\n}\n", + "timed_out": false + } + }, + "nonempty_sidecars": false + }, + "environment": { + "AGENTFS_BIN": "/home/ain3sh/factory/vfs/target/release/agentfs", + "AGENTFS_PROFILE": "1" + }, + "git_commit": "2b811f03493e861e5a7786592d25ac73b39e1aba", + "kept_temp": false, + "native": { + "run": { + "argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/native", + "duration_seconds": 1.128412741003558, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 15981, + "stdout_tail": "{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.2488119690096937, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/native/work\", \"duration_seconds\": 0.07269398200151045, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/native/work\", \"duration_seconds\": 0.1013916290103225, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/native/work\", \"duration_seconds\": 0.07468257400614675, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.0003027019993169233, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/native/work\", \"duration_seconds\": 0.1983038329926785, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/native/work\", \"duration_seconds\": 0.12846661900402978, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-s58r0ykm/native/mirror.git\", \"/tmp/agentfs-git-workload-s58r0ykm/native/work\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/native\", \"duration_seconds\": 0.28392924899526406, \"returncode\": 0, \"stderr_bytes\": 149, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-s58r0ykm/native/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/native/work\", \"duration_seconds\": 0.0879281010129489, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/native/work\", \"duration_seconds\": 0.10163158400973771, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.1312191629986046, \"clone\": 0.28396283299662173, \"diff\": 0.2488119690096937, \"edit\": 0.0003027019993169233, \"fsck\": 0.19831565700587817, \"read_search\": 0.005795197997940704, \"status\": 0.18957681799656712}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-s58r0ykm/native/work\", \"duration_seconds\": 0.004364182008430362, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 1.0581486070004757}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.2488119690096937, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/native/work", + "duration_seconds": 0.07269398200151045, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/native/work", + "duration_seconds": 0.1013916290103225, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/native/work", + "duration_seconds": 0.07468257400614675, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.0003027019993169233, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/native/work", + "duration_seconds": 0.1983038329926785, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/native/work", + "duration_seconds": 0.12846661900402978, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-s58r0ykm/native/mirror.git", + "/tmp/agentfs-git-workload-s58r0ykm/native/work" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/native", + "duration_seconds": 0.28392924899526406, + "returncode": 0, + "stderr_bytes": 149, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-s58r0ykm/native/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/native/work", + "duration_seconds": 0.0879281010129489, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/native/work", + "duration_seconds": 0.10163158400973771, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.1312191629986046, + "clone": 0.28396283299662173, + "diff": 0.2488119690096937, + "edit": 0.0003027019993169233, + "fsck": 0.19831565700587817, + "read_search": 0.005795197997940704, + "status": 0.18957681799656712 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-s58r0ykm/native/work", + "duration_seconds": 0.004364182008430362, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 1.0581486070004757 + } + }, + "parameters": { + "edit_files": 8, + "fixture_dirs": 8, + "fixture_file_size_bytes": 1024, + "fixture_files": 96, + "read_bytes": 4096, + "read_files": 64, + "search_token": "AGENTFS_TOKEN", + "skip_fsck": false, + "timeout_seconds": 180.0 + }, + "schema_version": 1, + "source": { + "kind": "source", + "mirror_head": "7d47056ea42636271ac020b86347fbbef49490aa", + "path": "/home/ain3sh/factory/vfs/.agents/benchmarks/fixtures/codex" + }, + "summary": { + "agentfs_base_unchanged": true, + "agentfs_seconds": 4.671860290996847, + "all_equivalent": true, + "correctness_passed": true, + "native_seconds": 1.0581486070004757, + "passed": true, + "performance_passed": false, + "phase_ratios": { + "checkout": { + "agentfs_seconds": 0.06298040700494312, + "native_seconds": 0.1312191629986046, + "ratio": 0.4799634867783211 + }, + "clone": { + "agentfs_seconds": 3.4255687719996786, + "native_seconds": 0.28396283299662173, + "ratio": 12.063440612456603 + }, + "diff": { + "agentfs_seconds": 0.07817813300061971, + "native_seconds": 0.2488119690096937, + "ratio": 0.3142056763256991 + }, + "edit": { + "agentfs_seconds": 0.018203587998868898, + "native_seconds": 0.0003027019993169233, + "ratio": 60.136992949987366 + }, + "fsck": { + "agentfs_seconds": 0.20209814199188258, + "native_seconds": 0.19831565700587817, + "ratio": 1.0190730527438503 + }, + "read_search": { + "agentfs_seconds": 0.010043239002698101, + "native_seconds": 0.005795197997940704, + "ratio": 1.7330277595807628 + }, + "status": { + "agentfs_seconds": 0.1734860779979499, + "native_seconds": 0.18957681799656712, + "ratio": 0.9151228501002239 + } + }, + "ratio": 4.415126816865664, + "threshold_failures": [ + { + "agentfs_seconds": 3.4255687719996786, + "native_seconds": 0.28396283299662173, + "phase": "clone", + "ratio": 12.063440612456603 + }, + { + "agentfs_seconds": 0.018203587998868898, + "native_seconds": 0.0003027019993169233, + "phase": "edit", + "ratio": 60.136992949987366 + } + ] + }, + "temp_dir": "/tmp/agentfs-git-workload-s58r0ykm" +} diff --git a/.agents/benchmarks/restructure-baseline/medians.json b/.agents/benchmarks/restructure-baseline/medians.json new file mode 100644 index 00000000..30b41269 --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians.json @@ -0,0 +1,288 @@ +{ + "agentfs_bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "forwarded_argv": [ + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "iteration_returncodes": [ + 0, + 0, + 0, + 0, + 0 + ], + "iteration_wall_seconds": [ + 12.041874304995872, + 10.64369312299823, + 10.765639696008293, + 11.422489012998994, + 11.208898140990641 + ], + "iterations": 5, + "label": "misc-a-rebaseline-run3", + "overall": { + "agentfs_seconds": { + "count": 5, + "max": 4.789309019004577, + "mean": 4.173267442599172, + "median": 4.0367547670030035, + "min": 3.9237991579866502, + "p25": 4.001881017000414, + "p75": 4.114593252001214, + "stdev": 0.3511386218460735 + }, + "native_seconds": { + "count": 5, + "max": 1.4025006750016473, + "mean": 1.0126429916010238, + "median": 0.8855433049902786, + "min": 0.8398232860054122, + "p25": 0.858039740996901, + "p75": 1.07730795101088, + "stdev": 0.23774656483219894 + }, + "ratio": { + "count": 5, + "max": 4.806671634700289, + "mean": 4.266396078519256, + "median": 4.445626725868477, + "min": 2.8533897261730115, + "p25": 4.4309511865483815, + "p75": 4.795341119306122, + "stdev": 0.8104735487784721 + } + }, + "phases": { + "checkout": { + "agentfs_seconds": { + "count": 5, + "max": 0.06318244199792389, + "mean": 0.057910432401695286, + "median": 0.056683568996959366, + "min": 0.05428599700098857, + "p25": 0.05482768401270732, + "p75": 0.060572469999897294, + "stdev": 0.0038414057574848726 + }, + "native_seconds": { + "count": 5, + "max": 0.24071779299993068, + "mean": 0.18195385900035035, + "median": 0.18452265500673093, + "min": 0.11891778999415692, + "p25": 0.16255415299383458, + "p75": 0.20305690400709864, + "stdev": 0.04542860263501151 + }, + "ratio": { + "count": 5, + "max": 0.5313119424858836, + "mean": 0.34009054339177636, + "median": 0.32826576225931586, + "min": 0.22776747547167447, + "p25": 0.27915115358489745, + "p75": 0.33395638315711046, + "stdev": 0.1151880610774632 + } + }, + "clone": { + "agentfs_seconds": { + "count": 5, + "max": 3.515449283993803, + "mean": 2.950434782798402, + "median": 2.8008923250017688, + "min": 2.769973303991719, + "p25": 2.777395411001635, + "p75": 2.8884635900030844, + "stdev": 0.31935627890583845 + }, + "native_seconds": { + "count": 5, + "max": 0.41264774699811824, + "mean": 0.35559882699744777, + "median": 0.3344773830031045, + "min": 0.3286946500011254, + "p25": 0.3300862679898273, + "p75": 0.37208808699506335, + "stdev": 0.036562671441939254 + }, + "ratio": { + "count": 5, + "max": 8.750632395565457, + "mean": 8.307397465733112, + "median": 8.51924991610298, + "min": 7.464349190621853, + "p25": 8.281496581686689, + "p75": 8.521259244688585, + "stdev": 0.49961904842881455 + } + }, + "diff": { + "agentfs_seconds": { + "count": 5, + "max": 0.06838756799697876, + "mean": 0.05834037900203839, + "median": 0.06564354000147432, + "min": 0.024519680009689182, + "p25": 0.06495013200037647, + "p75": 0.06820097500167321, + "stdev": 0.01896735376760473 + }, + "native_seconds": { + "count": 5, + "max": 0.39594387999386527, + "mean": 0.12749572600296233, + "median": 0.01741337300336454, + "min": 0.013687198006664403, + "p25": 0.014816572002018802, + "p75": 0.19561760700889863, + "stdev": 0.16916862836079494 + }, + "ratio": { + "count": 5, + "max": 4.996462238924309, + "mean": 2.1850678847070717, + "median": 1.6548821148608668, + "min": 0.172249095004903, + "p25": 0.3320260021247571, + "p75": 3.7697199726205226, + "stdev": 2.131980364824143 + } + }, + "edit": { + "agentfs_seconds": { + "count": 5, + "max": 0.00608031298907008, + "mean": 0.005196311196777969, + "median": 0.004993759008357301, + "min": 0.004524231000686996, + "p25": 0.004875056998571381, + "p75": 0.00550819598720409, + "stdev": 0.0006071768132413841 + }, + "native_seconds": { + "count": 5, + "max": 0.0010122020030394197, + "mean": 0.0007302397978492081, + "median": 0.0009361069969600067, + "min": 0.000326687004417181, + "p25": 0.00037815999530721456, + "p75": 0.0009980429895222187, + "stdev": 0.0003465608109482753 + }, + "ratio": { + "count": 5, + "max": 13.848824530863581, + "mean": 8.727012533346421, + "median": 6.0070153692763295, + "min": 5.0035510101102005, + "p25": 5.884152137620884, + "p75": 12.891519618861109, + "stdev": 4.269674546155932 + } + }, + "fsck": { + "agentfs_seconds": { + "count": 5, + "max": 0.23772984399693087, + "mean": 0.21839883019856643, + "median": 0.21496818200103007, + "min": 0.2023740630102111, + "p25": 0.21477365799364634, + "p75": 0.22214840399101377, + "stdev": 0.0129368831457691 + }, + "native_seconds": { + "count": 5, + "max": 0.23137624299852178, + "mean": 0.2009059926000191, + "median": 0.19252638499892782, + "min": 0.1741181089892052, + "p25": 0.1890194620063994, + "p75": 0.21748976400704123, + "stdev": 0.023089801725413604 + }, + "ratio": { + "count": 5, + "max": 1.1622803864861504, + "mean": 1.0936590596662537, + "median": 1.115554410866035, + "min": 0.9601176037439291, + "p25": 1.0930622187315187, + "p75": 1.1372806785036356, + "stdev": 0.07893797438120992 + } + }, + "read_search": { + "agentfs_seconds": { + "count": 5, + "max": 0.010672920994693413, + "mean": 0.008850710198748857, + "median": 0.008785623009316623, + "min": 0.007138228989788331, + "p25": 0.0078469020081684, + "p75": 0.009809875991777517, + "stdev": 0.0014304641135065828 + }, + "native_seconds": { + "count": 5, + "max": 0.010034471997641958, + "mean": 0.008122092401026747, + "median": 0.008877050000592135, + "min": 0.005955979009740986, + "p25": 0.0063567659963155165, + "p75": 0.009386195000843145, + "stdev": 0.0018461809648997337 + }, + "ratio": { + "count": 5, + "max": 1.475093010728843, + "mean": 1.1244383049346869, + "median": 1.1229340507304757, + "min": 0.7819945095279927, + "p25": 1.1050828812638387, + "p75": 1.1370870724222844, + "stdev": 0.24533821666898545 + } + }, + "status": { + "agentfs_seconds": { + "count": 5, + "max": 0.19046289600373711, + "mean": 0.17302955399791245, + "median": 0.17797551400144584, + "min": 0.1508024939976167, + "p25": 0.1672487969917711, + "p75": 0.17865806899499148, + "stdev": 0.014895666003419806 + }, + "native_seconds": { + "count": 5, + "max": 0.26516752400493715, + "mean": 0.13757860799960325, + "median": 0.12686727399704978, + "min": 0.018477466990589164, + "p25": 0.12191534000157844, + "p75": 0.15546543500386178, + "stdev": 0.08823834554466341 + }, + "ratio": { + "count": 5, + "max": 9.632029871415346, + "mean": 2.831268938087937, + "median": 1.3182973963455726, + "min": 0.6737554670973398, + "p25": 0.9700065740907021, + "p75": 1.562255381490723, + "stdev": 3.816706352680297 + } + } + }, + "warmup_iterations": 1 +} diff --git a/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-00.json b/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-00.json new file mode 100644 index 00000000..5fa086de --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-00.json @@ -0,0 +1,6630 @@ +{ + "agentfs": { + "bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "db_path": "/tmp/agentfs-git-workload-85r78ug0/home/.agentfs/run/git-workload-5f9c17332ed2467e865db70fa7e5dded/delta.db", + "per_phase_counters": { + "checkpoint_count": 7, + "label_count": 7, + "labels_aligned": true, + "phases": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 887987106, + "agentfs_batcher_commit_txns": 4783, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4679, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 969, + "connection_create_count": 2, + "connection_reuse_count": 31203, + "connection_wait_count": 31205, + "connection_wait_nanos": 11406857, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 91, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5285, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21783, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 36, + "fuse_dispatch_wait_count": 36, + "fuse_dispatch_wait_nanos": 905664, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52495943, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5025, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11702, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 617937107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 1276997, + "fuse_op_getattr_count": 5025, + "fuse_op_getattr_nanos": 58792718, + "fuse_op_lookup_count": 11702, + "fuse_op_lookup_nanos": 148907519, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 121030097, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27574689, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 915091, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1240634529, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 29136769, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 2, + "fuse_read_lane_wait_count": 16154, + "fuse_read_lane_wait_nanos": 1922885, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32006, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 2317111, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40290, + "lookup_delta_count": 7192, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6780, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32905, + "path_resolution_count": 7119, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "phase": "clone", + "seq": 1 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 2, + "agentfs_batcher_commit_latency_ns_total": 4446084, + "agentfs_batcher_commit_txns": 9, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 5, + "agentfs_batcher_drains_timer": 2, + "agentfs_batcher_enqueues": 9, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 14, + "attr_cache_hits": 190, + "attr_cache_misses": 1721, + "base_fast_inode_invalidations": 51, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 14, + "connection_create_count": 5, + "connection_reuse_count": 623, + "connection_wait_count": 628, + "connection_wait_nanos": 244739, + "dentry_cache_hits": 73, + "dentry_cache_misses": 56, + "fuse_adapter_attr_hits": 4622, + "fuse_adapter_attr_misses": 643, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 35, + "fuse_adapter_inval_entry_notifications": 10, + "fuse_adapter_inval_inode_notifications": 28, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 13, + "fuse_adapter_negative_misses": 35, + "fuse_callback_count": 5336, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 5, + "fuse_dispatch_parallel_tasks": 9, + "fuse_dispatch_wait_count": 9, + "fuse_dispatch_wait_nanos": 308456, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 738333, + "fuse_flush_count": 9, + "fuse_flush_ranges": 9, + "fuse_getattr_count": 5265, + "fuse_ino_file_resolutions": 3, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 50, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 9, + "fuse_op_create_nanos": 2240785, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 9, + "fuse_op_forget_nanos": 219679, + "fuse_op_getattr_count": 5265, + "fuse_op_getattr_nanos": 18198142, + "fuse_op_lookup_count": 50, + "fuse_op_lookup_nanos": 709873, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 15, + "fuse_op_other_nanos": 3009504, + "fuse_op_read_count": 8, + "fuse_op_read_nanos": 2282728, + "fuse_op_readdirplus_count": 4, + "fuse_op_readdirplus_nanos": 245755, + "fuse_op_setattr_count": 14, + "fuse_op_setattr_nanos": 4089726, + "fuse_op_write_count": 9, + "fuse_op_write_nanos": 232164, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 8, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 708, + "fuse_read_lane_wait_nanos": 69021, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 4, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5374, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 591393, + "fuse_write_count": 9, + "fuse_write_lane_wait_count": 42, + "fuse_write_lane_wait_nanos": 159331, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 1314, + "lookup_base_count": 0, + "lookup_count": 164, + "lookup_delta_count": 60, + "lookup_whiteout_count": 0, + "negative_cache_hits": 34, + "negative_cache_invalidations": 26, + "negative_cache_misses": 148, + "negative_lookup_count": 86, + "path_cache_hits": 73, + "path_cache_misses": 56, + "path_component_count": 124, + "path_resolution_count": 45, + "readdir_count": 0, + "readdir_plus_count": 2, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "checkout", + "seq": 2 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 4, + "agentfs_batcher_commit_latency_ns_total": 3485568, + "agentfs_batcher_commit_txns": 4, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 2, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 6, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 4, + "attr_cache_hits": 21, + "attr_cache_misses": 37, + "base_fast_inode_invalidations": 24, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 20, + "chunk_read_queries": 12, + "chunk_write_chunks": 20, + "connection_create_count": 0, + "connection_reuse_count": 1017, + "connection_wait_count": 1017, + "connection_wait_nanos": 263391, + "dentry_cache_hits": 20, + "dentry_cache_misses": 570, + "fuse_adapter_attr_hits": 1856, + "fuse_adapter_attr_misses": 9, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 289, + "fuse_adapter_inval_entry_notifications": 4, + "fuse_adapter_inval_inode_notifications": 8, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 3, + "fuse_adapter_negative_misses": 289, + "fuse_callback_count": 3562, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 4, + "fuse_dispatch_wait_count": 4, + "fuse_dispatch_wait_nanos": 79239, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 1181458, + "fuse_flush_count": 6, + "fuse_flush_ranges": 6, + "fuse_getattr_count": 1865, + "fuse_ino_file_resolutions": 2, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 294, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 2, + "fuse_op_create_nanos": 795094, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 4, + "fuse_op_forget_nanos": 46407, + "fuse_op_getattr_count": 1865, + "fuse_op_getattr_nanos": 1932275, + "fuse_op_lookup_count": 294, + "fuse_op_lookup_nanos": 3514936, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 2762, + "fuse_op_other_nanos": 2360037, + "fuse_op_read_count": 12, + "fuse_op_read_nanos": 2833429, + "fuse_op_readdirplus_count": 1381, + "fuse_op_readdirplus_nanos": 40914314, + "fuse_op_setattr_count": 6, + "fuse_op_setattr_nanos": 5081612, + "fuse_op_write_count": 10, + "fuse_op_write_nanos": 1098920, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 12, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 998, + "fuse_read_lane_wait_nanos": 72023, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1381, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 6332, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 1181458, + "fuse_write_count": 10, + "fuse_write_lane_wait_count": 14, + "fuse_write_lane_wait_nanos": 3521, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 30, + "lookup_base_count": 0, + "lookup_count": 598, + "lookup_delta_count": 295, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7, + "negative_cache_invalidations": 4, + "negative_cache_misses": 873, + "negative_lookup_count": 578, + "path_cache_hits": 20, + "path_cache_misses": 570, + "path_component_count": 4606, + "path_resolution_count": 977, + "readdir_count": 0, + "readdir_plus_count": 688, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "status", + "seq": 3 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 1, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 8, + "connection_wait_count": 8, + "connection_wait_nanos": 6243, + "dentry_cache_hits": 1, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 69, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 1, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 1, + "fuse_callback_count": 76, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 69, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 1, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 69, + "fuse_op_getattr_nanos": 76036, + "fuse_op_lookup_count": 1, + "fuse_op_lookup_nanos": 25596, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 6, + "fuse_op_read_nanos": 986609, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 6, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 2, + "fuse_read_lane_wait_nanos": 609, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 76, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 2, + "lookup_delta_count": 1, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 2, + "negative_lookup_count": 0, + "path_cache_hits": 1, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "read_search", + "seq": 4 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 1196605, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 8, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 8, + "attr_cache_hits": 0, + "attr_cache_misses": 24, + "base_fast_inode_invalidations": 16, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 32, + "connection_wait_count": 32, + "connection_wait_nanos": 11473, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 16, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 24, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 6398, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 16, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 16, + "fuse_op_getattr_nanos": 28995, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 8, + "fuse_op_other_nanos": 1489396, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 8, + "fuse_op_setattr_nanos": 2161602, + "fuse_op_write_count": 8, + "fuse_op_write_nanos": 465794, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 8, + "fuse_read_lane_wait_nanos": 1070, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 40, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 6398, + "fuse_write_count": 8, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 1782, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 16, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 8, + "wal_checkpoint_nanos": 1363353 + }, + "phase": "edit", + "seq": 5 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 1149, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 16, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 1165, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 1149, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 16, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 1149, + "fuse_op_getattr_nanos": 653522, + "fuse_op_lookup_count": 16, + "fuse_op_lookup_nanos": 15535, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 12, + "fuse_op_other_nanos": 6013, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 1177, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 16, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "diff", + "seq": 6 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 3, + "attr_cache_misses": 2, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 1, + "chunk_read_queries": 1, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 15, + "connection_wait_count": 15, + "connection_wait_nanos": 11914, + "dentry_cache_hits": 4, + "dentry_cache_misses": 2, + "fuse_adapter_attr_hits": 44, + "fuse_adapter_attr_misses": 1, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 4, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 244, + "fuse_adapter_negative_misses": 4, + "fuse_callback_count": 313, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 45, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 248, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 45, + "fuse_op_getattr_nanos": 73185, + "fuse_op_lookup_count": 248, + "fuse_op_lookup_nanos": 318435, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 36, + "fuse_op_other_nanos": 12828, + "fuse_op_read_count": 2, + "fuse_op_read_nanos": 610349, + "fuse_op_readdirplus_count": 18, + "fuse_op_readdirplus_nanos": 515792, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 2, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 15, + "fuse_read_lane_wait_nanos": 2854, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 18, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 349, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 2, + "lookup_base_count": 0, + "lookup_count": 8, + "lookup_delta_count": 4, + "lookup_whiteout_count": 0, + "negative_cache_hits": 244, + "negative_cache_invalidations": 0, + "negative_cache_misses": 9, + "negative_lookup_count": 2, + "path_cache_hits": 4, + "path_cache_misses": 2, + "path_component_count": 42, + "path_resolution_count": 10, + "readdir_count": 0, + "readdir_plus_count": 9, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "fsck", + "seq": 7 + } + ] + }, + "profile_counters": { + "last_by_source": { + "agentfs": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32901, + "connection_wait_count": 32908, + "connection_wait_nanos": 11955194, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 2483186, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3828351 + }, + "fuse_session": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32901, + "connection_wait_count": 32908, + "connection_wait_nanos": 11955194, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 2483186, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3828351 + }, + "phase-checkpoint-1": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 887987106, + "agentfs_batcher_commit_txns": 4783, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4679, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 969, + "connection_create_count": 2, + "connection_reuse_count": 31203, + "connection_wait_count": 31205, + "connection_wait_nanos": 11406857, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 91, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5285, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21783, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 36, + "fuse_dispatch_wait_count": 36, + "fuse_dispatch_wait_nanos": 905664, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52495943, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5025, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11702, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 617937107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 1276997, + "fuse_op_getattr_count": 5025, + "fuse_op_getattr_nanos": 58792718, + "fuse_op_lookup_count": 11702, + "fuse_op_lookup_nanos": 148907519, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 121030097, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27574689, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 915091, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1240634529, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 29136769, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 2, + "fuse_read_lane_wait_count": 16154, + "fuse_read_lane_wait_nanos": 1922885, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32006, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 2317111, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40290, + "lookup_delta_count": 7192, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6780, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32905, + "path_resolution_count": 7119, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "phase-checkpoint-2": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 892433190, + "agentfs_batcher_commit_txns": 4792, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4684, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26112, + "attr_cache_misses": 30774, + "base_fast_inode_invalidations": 15079, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create_count": 7, + "connection_reuse_count": 31826, + "connection_wait_count": 31833, + "connection_wait_nanos": 11651596, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4713, + "fuse_adapter_attr_misses": 5577, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5298, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27119, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 45, + "fuse_dispatch_wait_count": 45, + "fuse_dispatch_wait_nanos": 1214120, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10290, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11752, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 620177892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 1496676, + "fuse_op_getattr_count": 10290, + "fuse_op_getattr_nanos": 76990860, + "fuse_op_lookup_count": 11752, + "fuse_op_lookup_nanos": 149617392, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 124039601, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 29857417, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1160846, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1244724255, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 29368933, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 16862, + "fuse_read_lane_wait_nanos": 1991906, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37380, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 2476442, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20650, + "lookup_base_count": 23, + "lookup_count": 40454, + "lookup_delta_count": 7252, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6814, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33029, + "path_resolution_count": 7164, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "phase-checkpoint-3": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 895918758, + "agentfs_batcher_commit_txns": 4796, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26133, + "attr_cache_misses": 30811, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32843, + "connection_wait_count": 32850, + "connection_wait_nanos": 11914987, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6569, + "fuse_adapter_attr_misses": 5586, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5301, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30681, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 12155, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12046, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 12155, + "fuse_op_getattr_nanos": 78923135, + "fuse_op_lookup_count": 12046, + "fuse_op_lookup_nanos": 153132328, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 126399638, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 32690846, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 42075160, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1249805867, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 30467853, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17860, + "fuse_read_lane_wait_nanos": 2063929, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43712, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 2479963, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20680, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6821, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "phase-checkpoint-4": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 895918758, + "agentfs_batcher_commit_txns": 4796, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26134, + "attr_cache_misses": 30811, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32851, + "connection_wait_count": 32858, + "connection_wait_nanos": 11921230, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6638, + "fuse_adapter_attr_misses": 5586, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5301, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30757, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 12224, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12047, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 12224, + "fuse_op_getattr_nanos": 78999171, + "fuse_op_lookup_count": 12047, + "fuse_op_lookup_nanos": 153157924, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 126399638, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33677455, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 42075160, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1249805867, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 30467853, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17862, + "fuse_read_lane_wait_nanos": 2064538, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43788, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 2479963, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20680, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6821, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "phase-checkpoint-5": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26134, + "attr_cache_misses": 30835, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32883, + "connection_wait_count": 32890, + "connection_wait_nanos": 11932703, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6654, + "fuse_adapter_attr_misses": 5586, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5301, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30781, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12240, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12047, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 12240, + "fuse_op_getattr_nanos": 79028166, + "fuse_op_lookup_count": 12047, + "fuse_op_lookup_nanos": 153157924, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 127889034, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33677455, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 42075160, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 2065608, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43828, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 2481745, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20696, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6821, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3545653 + }, + "phase-checkpoint-6": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26134, + "attr_cache_misses": 30835, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32883, + "connection_wait_count": 32890, + "connection_wait_nanos": 11932703, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7803, + "fuse_adapter_attr_misses": 5586, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5317, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31946, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13389, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12063, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13389, + "fuse_op_getattr_nanos": 79681688, + "fuse_op_lookup_count": 12063, + "fuse_op_lookup_nanos": 153173459, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 127895047, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33677455, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 42075160, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 2065608, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45005, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 2481745, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20696, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6837, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3545653 + }, + "phase-checkpoint-7": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32898, + "connection_wait_count": 32905, + "connection_wait_nanos": 11944617, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 2481745, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3545653 + }, + "run_parent": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32901, + "connection_wait_count": 32908, + "connection_wait_nanos": 11955194, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 2483186, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3828351 + } + }, + "max_counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32901, + "connection_wait_count": 32908, + "connection_wait_nanos": 11955194, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 2483186, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3828351 + }, + "summary_count": 10 + }, + "profile_enabled": true, + "profile_summary_count": 10, + "session": "git-workload-5f9c17332ed2467e865db70fa7e5dded" + }, + "agentfs_overlay": { + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 887987106, + "agentfs_batcher_commit_txns": 4783, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4679, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 969, + "connection_create_count": 2, + "connection_reuse_count": 31203, + "connection_wait_count": 31205, + "connection_wait_nanos": 11406857, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 91, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5285, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21783, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 36, + "fuse_dispatch_wait_count": 36, + "fuse_dispatch_wait_nanos": 905664, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52495943, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5025, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11702, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 617937107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 1276997, + "fuse_op_getattr_count": 5025, + "fuse_op_getattr_nanos": 58792718, + "fuse_op_lookup_count": 11702, + "fuse_op_lookup_nanos": 148907519, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 121030097, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27574689, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 915091, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1240634529, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 29136769, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 2, + "fuse_read_lane_wait_count": 16154, + "fuse_read_lane_wait_nanos": 1922885, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32006, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 2317111, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40290, + "lookup_delta_count": 7192, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6780, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32905, + "path_resolution_count": 7119, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 892433190, + "agentfs_batcher_commit_txns": 4792, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4684, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26112, + "attr_cache_misses": 30774, + "base_fast_inode_invalidations": 15079, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create_count": 7, + "connection_reuse_count": 31826, + "connection_wait_count": 31833, + "connection_wait_nanos": 11651596, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4713, + "fuse_adapter_attr_misses": 5577, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5298, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27119, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 45, + "fuse_dispatch_wait_count": 45, + "fuse_dispatch_wait_nanos": 1214120, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10290, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11752, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 620177892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 1496676, + "fuse_op_getattr_count": 10290, + "fuse_op_getattr_nanos": 76990860, + "fuse_op_lookup_count": 11752, + "fuse_op_lookup_nanos": 149617392, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 124039601, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 29857417, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1160846, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1244724255, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 29368933, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 16862, + "fuse_read_lane_wait_nanos": 1991906, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37380, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 2476442, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20650, + "lookup_base_count": 23, + "lookup_count": 40454, + "lookup_delta_count": 7252, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6814, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33029, + "path_resolution_count": 7164, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 895918758, + "agentfs_batcher_commit_txns": 4796, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26133, + "attr_cache_misses": 30811, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32843, + "connection_wait_count": 32850, + "connection_wait_nanos": 11914987, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6569, + "fuse_adapter_attr_misses": 5586, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5301, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30681, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 12155, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12046, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 12155, + "fuse_op_getattr_nanos": 78923135, + "fuse_op_lookup_count": 12046, + "fuse_op_lookup_nanos": 153132328, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 126399638, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 32690846, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 42075160, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1249805867, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 30467853, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17860, + "fuse_read_lane_wait_nanos": 2063929, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43712, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 2479963, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20680, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6821, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 895918758, + "agentfs_batcher_commit_txns": 4796, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26134, + "attr_cache_misses": 30811, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32851, + "connection_wait_count": 32858, + "connection_wait_nanos": 11921230, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6638, + "fuse_adapter_attr_misses": 5586, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5301, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30757, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 12224, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12047, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 12224, + "fuse_op_getattr_nanos": 78999171, + "fuse_op_lookup_count": 12047, + "fuse_op_lookup_nanos": 153157924, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 126399638, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33677455, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 42075160, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1249805867, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 30467853, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17862, + "fuse_read_lane_wait_nanos": 2064538, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43788, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 2479963, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20680, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6821, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26134, + "attr_cache_misses": 30835, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32883, + "connection_wait_count": 32890, + "connection_wait_nanos": 11932703, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6654, + "fuse_adapter_attr_misses": 5586, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5301, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30781, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12240, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12047, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 12240, + "fuse_op_getattr_nanos": 79028166, + "fuse_op_lookup_count": 12047, + "fuse_op_lookup_nanos": 153157924, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 127889034, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33677455, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 42075160, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 2065608, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43828, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 2481745, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20696, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6821, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3545653 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26134, + "attr_cache_misses": 30835, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32883, + "connection_wait_count": 32890, + "connection_wait_nanos": 11932703, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7803, + "fuse_adapter_attr_misses": 5586, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5317, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31946, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13389, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12063, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13389, + "fuse_op_getattr_nanos": 79681688, + "fuse_op_lookup_count": 12063, + "fuse_op_lookup_nanos": 153173459, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 127895047, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33677455, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 42075160, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 2065608, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45005, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 2481745, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20696, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6837, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3545653 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32898, + "connection_wait_count": 32905, + "connection_wait_nanos": 11944617, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 2481745, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3545653 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32901, + "connection_wait_count": 32908, + "connection_wait_nanos": 11955194, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 2483186, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3828351 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "agentfs" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32901, + "connection_wait_count": 32908, + "connection_wait_nanos": 11955194, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 2483186, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3828351 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "fuse_session" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32901, + "connection_wait_count": 32908, + "connection_wait_nanos": 11955194, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 2483186, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3828351 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "run_parent" + } + ], + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-5f9c17332ed2467e865db70fa7e5dded", + "--no-default-allows", + "--", + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/agentfs-base", + "duration_seconds": 4.418804612010717, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 887987106, + "agentfs_batcher_commit_txns": 4783, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4679, + "agentfs_batcher_drains_timer": 17, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 969, + "connection_create_count": 2, + "connection_reuse_count": 31203, + "connection_wait_count": 31205, + "connection_wait_nanos": 11406857, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 91, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5285, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21783, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 36, + "fuse_dispatch_wait_count": 36, + "fuse_dispatch_wait_nanos": 905664, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52495943, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5025, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11702, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 617937107, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 1276997, + "fuse_op_getattr_count": 5025, + "fuse_op_getattr_nanos": 58792718, + "fuse_op_lookup_count": 11702, + "fuse_op_lookup_nanos": 148907519, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 121030097, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27574689, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 915091, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1240634529, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 29136769, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 2, + "fuse_read_lane_wait_count": 16154, + "fuse_read_lane_wait_nanos": 1922885, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32006, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 2317111, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40290, + "lookup_delta_count": 7192, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6780, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14258, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32905, + "path_resolution_count": 7119, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 892433190, + "agentfs_batcher_commit_txns": 4792, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4684, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26112, + "attr_cache_misses": 30774, + "base_fast_inode_invalidations": 15079, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create_count": 7, + "connection_reuse_count": 31826, + "connection_wait_count": 31833, + "connection_wait_nanos": 11651596, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4713, + "fuse_adapter_attr_misses": 5577, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5298, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27119, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 45, + "fuse_dispatch_wait_count": 45, + "fuse_dispatch_wait_nanos": 1214120, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10290, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11752, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 620177892, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 42, + "fuse_op_forget_nanos": 1496676, + "fuse_op_getattr_count": 10290, + "fuse_op_getattr_nanos": 76990860, + "fuse_op_lookup_count": 11752, + "fuse_op_lookup_nanos": 149617392, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 124039601, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 29857417, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1160846, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1244724255, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 29368933, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 16862, + "fuse_read_lane_wait_nanos": 1991906, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37380, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 2476442, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20650, + "lookup_base_count": 23, + "lookup_count": 40454, + "lookup_delta_count": 7252, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6814, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14344, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33029, + "path_resolution_count": 7164, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 895918758, + "agentfs_batcher_commit_txns": 4796, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26133, + "attr_cache_misses": 30811, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32843, + "connection_wait_count": 32850, + "connection_wait_nanos": 11914987, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6569, + "fuse_adapter_attr_misses": 5586, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5301, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30681, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 12155, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12046, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 12155, + "fuse_op_getattr_nanos": 78923135, + "fuse_op_lookup_count": 12046, + "fuse_op_lookup_nanos": 153132328, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 126399638, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 32690846, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 42075160, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1249805867, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 30467853, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17860, + "fuse_read_lane_wait_nanos": 2063929, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43712, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 2479963, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20680, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6821, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14922, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 895918758, + "agentfs_batcher_commit_txns": 4796, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26134, + "attr_cache_misses": 30811, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32851, + "connection_wait_count": 32858, + "connection_wait_nanos": 11921230, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6638, + "fuse_adapter_attr_misses": 5586, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5301, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30757, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 12224, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12047, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 12224, + "fuse_op_getattr_nanos": 78999171, + "fuse_op_lookup_count": 12047, + "fuse_op_lookup_nanos": 153157924, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 126399638, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33677455, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 42075160, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1249805867, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 30467853, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17862, + "fuse_read_lane_wait_nanos": 2064538, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43788, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 2479963, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20680, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6821, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2182300 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26134, + "attr_cache_misses": 30835, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32883, + "connection_wait_count": 32890, + "connection_wait_nanos": 11932703, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6654, + "fuse_adapter_attr_misses": 5586, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5301, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30781, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12240, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12047, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 12240, + "fuse_op_getattr_nanos": 79028166, + "fuse_op_lookup_count": 12047, + "fuse_op_lookup_nanos": 153157924, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 127889034, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33677455, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 42075160, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 2065608, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43828, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 2481745, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20696, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6821, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3545653 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26134, + "attr_cache_misses": 30835, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32883, + "connection_wait_count": 32890, + "connection_wait_nanos": 11932703, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7803, + "fuse_adapter_attr_misses": 5586, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5317, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31946, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13389, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12063, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13389, + "fuse_op_getattr_nanos": 79681688, + "fuse_op_lookup_count": 12063, + "fuse_op_lookup_nanos": 153173459, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 127895047, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 33677455, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 42075160, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17870, + "fuse_read_lane_wait_nanos": 2065608, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45005, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 2481745, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20696, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6837, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14922, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37635, + "path_resolution_count": 8141, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3545653 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32898, + "connection_wait_count": 32905, + "connection_wait_nanos": 11944617, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 2481745, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3545653 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32901, + "connection_wait_count": 32908, + "connection_wait_nanos": 11955194, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 2483186, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3828351 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "agentfs" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32901, + "connection_wait_count": 32908, + "connection_wait_nanos": 11955194, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 2483186, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3828351 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "fuse_session" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 897115363, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4694, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1347584, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26137, + "attr_cache_misses": 30837, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32901, + "connection_wait_count": 32908, + "connection_wait_nanos": 11955194, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7847, + "fuse_adapter_attr_misses": 5587, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5561, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32259, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 49, + "fuse_dispatch_wait_count": 49, + "fuse_dispatch_wait_nanos": 1293359, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13434, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12311, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 620972986, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1824, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 1543083, + "fuse_op_getattr_count": 13434, + "fuse_op_getattr_nanos": 79754873, + "fuse_op_lookup_count": 12311, + "fuse_op_lookup_nanos": 153491894, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1813, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 127907875, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 34287804, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 42590952, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1251967469, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 30933647, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17885, + "fuse_read_lane_wait_nanos": 2068462, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45354, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 2483186, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41062, + "lookup_delta_count": 7552, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7081, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14924, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37677, + "path_resolution_count": 8151, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3828351 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "run_parent" + } + ], + "returncode": 0, + "stderr_bytes": 40604, + "stderr_tail": "4,\"agentfs_batcher_pending_max_bytes\":1347584,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26134,\"attr_cache_misses\":30835,\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":267,\"chunk_read_queries\":122,\"chunk_write_chunks\":1003,\"connection_create_count\":7,\"connection_reuse_count\":32883,\"connection_wait_count\":32890,\"connection_wait_nanos\":11932703,\"dentry_cache_hits\":26125,\"dentry_cache_misses\":14052,\"fuse_adapter_attr_hits\":7803,\"fuse_adapter_attr_misses\":5586,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6734,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5317,\"fuse_adapter_negative_misses\":6734,\"fuse_callback_count\":31946,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":49,\"fuse_dispatch_wait_count\":49,\"fuse_dispatch_wait_nanos\":1293359,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13389,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12063,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":620972986,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":1824,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":1543083,\"fuse_op_getattr_count\":13389,\"fuse_op_getattr_nanos\":79681688,\"fuse_op_lookup_count\":12063,\"fuse_op_lookup_nanos\":153173459,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1813,\"fuse_op_other_count\":3581,\"fuse_op_other_nanos\":127895047,\"fuse_op_read_count\":239,\"fuse_op_read_nanos\":33677455,\"fuse_op_readdirplus_count\":1407,\"fuse_op_readdirplus_nanos\":42075160,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1251967469,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":30933647,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":239,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17870,\"fuse_read_lane_wait_nanos\":2065608,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1407,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45005,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":2481745,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20696,\"lookup_base_count\":23,\"lookup_count\":41054,\"lookup_delta_count\":7548,\"lookup_whiteout_count\":0,\"negative_cache_hits\":6837,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46085,\"negative_lookup_count\":14922,\"path_cache_hits\":26125,\"path_cache_misses\":14052,\"path_component_count\":37635,\"path_resolution_count\":8141,\"readdir_count\":1,\"readdir_plus_count\":701,\"wal_checkpoint_count\":11,\"wal_checkpoint_nanos\":3545653},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"phase-checkpoint-6\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":897115363,\"agentfs_batcher_commit_txns\":4804,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4694,\"agentfs_batcher_drains_timer\":19,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1347584,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26137,\"attr_cache_misses\":30837,\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create_count\":7,\"connection_reuse_count\":32898,\"connection_wait_count\":32905,\"connection_wait_nanos\":11944617,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7847,\"fuse_adapter_attr_misses\":5587,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5561,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32259,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":49,\"fuse_dispatch_wait_count\":49,\"fuse_dispatch_wait_nanos\":1293359,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13434,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12311,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":620972986,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":1824,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":1543083,\"fuse_op_getattr_count\":13434,\"fuse_op_getattr_nanos\":79754873,\"fuse_op_lookup_count\":12311,\"fuse_op_lookup_nanos\":153491894,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1813,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":127907875,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34287804,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":42590952,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1251967469,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":30933647,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17885,\"fuse_read_lane_wait_nanos\":2068462,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45354,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":2481745,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20698,\"lookup_base_count\":23,\"lookup_count\":41062,\"lookup_delta_count\":7552,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7081,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14924,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37677,\"path_resolution_count\":8151,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":11,\"wal_checkpoint_nanos\":3545653},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"phase-checkpoint-7\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":897115363,\"agentfs_batcher_commit_txns\":4804,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4694,\"agentfs_batcher_drains_timer\":19,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1347584,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26137,\"attr_cache_misses\":30837,\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create_count\":7,\"connection_reuse_count\":32901,\"connection_wait_count\":32908,\"connection_wait_nanos\":11955194,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7847,\"fuse_adapter_attr_misses\":5587,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5561,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32259,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":49,\"fuse_dispatch_wait_count\":49,\"fuse_dispatch_wait_nanos\":1293359,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13434,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12311,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":620972986,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":1824,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":1543083,\"fuse_op_getattr_count\":13434,\"fuse_op_getattr_nanos\":79754873,\"fuse_op_lookup_count\":12311,\"fuse_op_lookup_nanos\":153491894,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1813,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":127907875,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34287804,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":42590952,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1251967469,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":30933647,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17885,\"fuse_read_lane_wait_nanos\":2068462,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45354,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10270,\"fuse_write_lane_wait_nanos\":2483186,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20698,\"lookup_base_count\":23,\"lookup_count\":41062,\"lookup_delta_count\":7552,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7081,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14924,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37677,\"path_resolution_count\":8151,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":3828351},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"agentfs\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":897115363,\"agentfs_batcher_commit_txns\":4804,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4694,\"agentfs_batcher_drains_timer\":19,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1347584,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26137,\"attr_cache_misses\":30837,\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create_count\":7,\"connection_reuse_count\":32901,\"connection_wait_count\":32908,\"connection_wait_nanos\":11955194,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7847,\"fuse_adapter_attr_misses\":5587,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5561,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32259,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":49,\"fuse_dispatch_wait_count\":49,\"fuse_dispatch_wait_nanos\":1293359,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13434,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12311,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":620972986,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":1824,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":1543083,\"fuse_op_getattr_count\":13434,\"fuse_op_getattr_nanos\":79754873,\"fuse_op_lookup_count\":12311,\"fuse_op_lookup_nanos\":153491894,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1813,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":127907875,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34287804,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":42590952,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1251967469,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":30933647,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17885,\"fuse_read_lane_wait_nanos\":2068462,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45354,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10270,\"fuse_write_lane_wait_nanos\":2483186,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20698,\"lookup_base_count\":23,\"lookup_count\":41062,\"lookup_delta_count\":7552,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7081,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14924,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37677,\"path_resolution_count\":8151,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":3828351},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"fuse_session\"}\n\nSession: git-workload-5f9c17332ed2467e865db70fa7e5dded\n\nTo resume this session:\n agentfs run --session git-workload-5f9c17332ed2467e865db70fa7e5dded\n\nTo see what changed:\n agentfs diff git-workload-5f9c17332ed2467e865db70fa7e5dded\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":897115363,\"agentfs_batcher_commit_txns\":4804,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4694,\"agentfs_batcher_drains_timer\":19,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1347584,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26137,\"attr_cache_misses\":30837,\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create_count\":7,\"connection_reuse_count\":32901,\"connection_wait_count\":32908,\"connection_wait_nanos\":11955194,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7847,\"fuse_adapter_attr_misses\":5587,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5561,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32259,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":49,\"fuse_dispatch_wait_count\":49,\"fuse_dispatch_wait_nanos\":1293359,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13434,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12311,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":620972986,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":1824,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":1543083,\"fuse_op_getattr_count\":13434,\"fuse_op_getattr_nanos\":79754873,\"fuse_op_lookup_count\":12311,\"fuse_op_lookup_nanos\":153491894,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1813,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":127907875,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":34287804,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":42590952,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1251967469,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":30933647,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17885,\"fuse_read_lane_wait_nanos\":2068462,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45354,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10270,\"fuse_write_lane_wait_nanos\":2483186,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20698,\"lookup_base_count\":23,\"lookup_count\":41062,\"lookup_delta_count\":7552,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7081,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14924,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37677,\"path_resolution_count\":8151,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":3828351},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"run_parent\"}\n", + "stdout_bytes": 18918, + "stdout_tail": "2026-07-03T11:38:53.399295Z INFO agentfs::fuser::session: resolved FUSE dispatch mode: parallel workers=7 queue_capacity=28\n2026-07-03T11:38:53.399773Z INFO agentfs::fuse: advertising FUSE_OVER_IO_URING\n2026-07-03T11:38:53.399941Z INFO agentfs::fuser::uring: starting fuse-over-io_uring queues nr_queues=14 depth=4\n2026-07-03T11:38:54.767217Z WARN agentfs::fuser::request: Request RequestId(25785): Failed to send reply: No such file or directory (os error 2)\n2026-07-03T11:38:55.767213Z WARN agentfs::fuser::request: Request RequestId(46131): Failed to send reply: No such file or directory (os error 2)\n{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.01879543399263639, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work\", \"duration_seconds\": 0.009126688994001597, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work\", \"duration_seconds\": 0.0046758670068811625, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work\", \"duration_seconds\": 0.004955205004080199, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.005346574995201081, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work\", \"duration_seconds\": 0.17578380899794865, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work\", \"duration_seconds\": 0.07431378299952485, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-85r78ug0/agentfs-base/mirror.git\", \"/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/agentfs-base\", \"duration_seconds\": 3.156747261993587, \"returncode\": 0, \"stderr_bytes\": 2340, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\nUpdating files: 37% (1750/4644)\\nUpdating files: 38% (1765/4644)\\nUpdating files: 39% (1812/4644)\\nUpdating files: 40% (1858/4644)\\nUpdating files: 41% (1905/4644)\\nUpdating files: 42% (1951/4644)\\nUpdating files: 43% (1997/4644)\\nUpdating files: 44% (2044/4644)\\nUpdating files: 45% (2090/4644)\\nUpdating files: 46% (2137/4644)\\nUpdating files: 47% (2183/4644)\\nUpdating files: 48% (2230/4644)\\nUpdating files: 49% (2276/4644)\\nUpdating files: 50% (2322/4644)\\nUpdating files: 51% (2369/4644)\\nUpdating files: 52% (2415/4644)\\nUpdating files: 53% (2462/4644)\\nUpdating files: 54% (2508/4644)\\nUpdating files: 55% (2555/4644)\\nUpdating files: 56% (2601/4644)\\nUpdating files: 57% (2648/4644)\\nUpdating files: 58% (2694/4644)\\nUpdating files: 59% (2740/4644)\\nUpdating files: 60% (2787/4644)\\nUpdating files: 61% (2833/4644)\\nUpdating files: 62% (2880/4644)\\nUpdating files: 63% (2926/4644)\\nUpdating files: 64% (2973/4644)\\nUpdating files: 65% (3019/4644)\\nUpdating files: 66% (3066/4644)\\nUpdating files: 67% (3112/4644)\\nUpdating files: 68% (3158/4644)\\nUpdating files: 69% (3205/4644)\\nUpdating files: 70% (3251/4644)\\nUpdating files: 70% (3265/4644)\\nUpdating files: 71% (3298/4644)\\nUpdating files: 72% (3344/4644)\\nUpdating files: 73% (3391/4644)\\nUpdating files: 74% (3437/4644)\\nUpdating files: 75% (3483/4644)\\nUpdating files: 76% (3530/4644)\\nUpdating files: 77% (3576/4644)\\nUpdating files: 78% (3623/4644)\\nUpdating files: 79% (3669/4644)\\nUpdating files: 80% (3716/4644)\\nUpdating files: 81% (3762/4644)\\nUpdating files: 82% (3809/4644)\\nUpdating files: 83% (3855/4644)\\nUpdating files: 84% (3901/4644)\\nUpdating files: 85% (3948/4644)\\nUpdating files: 86% (3994/4644)\\nUpdating files: 87% (4041/4644)\\nUpdating files: 88% (4087/4644)\\nUpdating files: 89% (4134/4644)\\nUpdating files: 90% (4180/4644)\\nUpdating files: 91% (4227/4644)\\nUpdating files: 92% (4273/4644)\\nUpdating files: 93% (4319/4644)\\nUpdating files: 94% (4366/4644)\\nUpdating files: 95% (4412/4644)\\nUpdating files: 96% (4459/4644)\\nUpdating files: 97% (4505/4644)\\nUpdating files: 98% (4552/4644)\\nUpdating files: 99% (4598/4644)\\nUpdating files: 100% (4644/4644)\\nUpdating files: 100% (4644/4644), done.\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work\", \"duration_seconds\": 0.057441728000412695, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work\", \"duration_seconds\": 0.12273075699340552, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.0770113600010518, \"clone\": 3.1568224640068365, \"diff\": 0.01879543399263639, \"edit\": 0.005346574995201081, \"fsck\": 0.1757984619907802, \"read_search\": 0.007632192995515652, \"status\": 0.18019827299576718}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work\", \"duration_seconds\": 0.005131165991770104, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 4.322347755994997}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.01879543399263639, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work", + "duration_seconds": 0.009126688994001597, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work", + "duration_seconds": 0.0046758670068811625, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work", + "duration_seconds": 0.004955205004080199, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.005346574995201081, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work", + "duration_seconds": 0.17578380899794865, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work", + "duration_seconds": 0.07431378299952485, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-85r78ug0/agentfs-base/mirror.git", + "/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/agentfs-base", + "duration_seconds": 3.156747261993587, + "returncode": 0, + "stderr_bytes": 2340, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\nUpdating files: 37% (1750/4644)\nUpdating files: 38% (1765/4644)\nUpdating files: 39% (1812/4644)\nUpdating files: 40% (1858/4644)\nUpdating files: 41% (1905/4644)\nUpdating files: 42% (1951/4644)\nUpdating files: 43% (1997/4644)\nUpdating files: 44% (2044/4644)\nUpdating files: 45% (2090/4644)\nUpdating files: 46% (2137/4644)\nUpdating files: 47% (2183/4644)\nUpdating files: 48% (2230/4644)\nUpdating files: 49% (2276/4644)\nUpdating files: 50% (2322/4644)\nUpdating files: 51% (2369/4644)\nUpdating files: 52% (2415/4644)\nUpdating files: 53% (2462/4644)\nUpdating files: 54% (2508/4644)\nUpdating files: 55% (2555/4644)\nUpdating files: 56% (2601/4644)\nUpdating files: 57% (2648/4644)\nUpdating files: 58% (2694/4644)\nUpdating files: 59% (2740/4644)\nUpdating files: 60% (2787/4644)\nUpdating files: 61% (2833/4644)\nUpdating files: 62% (2880/4644)\nUpdating files: 63% (2926/4644)\nUpdating files: 64% (2973/4644)\nUpdating files: 65% (3019/4644)\nUpdating files: 66% (3066/4644)\nUpdating files: 67% (3112/4644)\nUpdating files: 68% (3158/4644)\nUpdating files: 69% (3205/4644)\nUpdating files: 70% (3251/4644)\nUpdating files: 70% (3265/4644)\nUpdating files: 71% (3298/4644)\nUpdating files: 72% (3344/4644)\nUpdating files: 73% (3391/4644)\nUpdating files: 74% (3437/4644)\nUpdating files: 75% (3483/4644)\nUpdating files: 76% (3530/4644)\nUpdating files: 77% (3576/4644)\nUpdating files: 78% (3623/4644)\nUpdating files: 79% (3669/4644)\nUpdating files: 80% (3716/4644)\nUpdating files: 81% (3762/4644)\nUpdating files: 82% (3809/4644)\nUpdating files: 83% (3855/4644)\nUpdating files: 84% (3901/4644)\nUpdating files: 85% (3948/4644)\nUpdating files: 86% (3994/4644)\nUpdating files: 87% (4041/4644)\nUpdating files: 88% (4087/4644)\nUpdating files: 89% (4134/4644)\nUpdating files: 90% (4180/4644)\nUpdating files: 91% (4227/4644)\nUpdating files: 92% (4273/4644)\nUpdating files: 93% (4319/4644)\nUpdating files: 94% (4366/4644)\nUpdating files: 95% (4412/4644)\nUpdating files: 96% (4459/4644)\nUpdating files: 97% (4505/4644)\nUpdating files: 98% (4552/4644)\nUpdating files: 99% (4598/4644)\nUpdating files: 100% (4644/4644)\nUpdating files: 100% (4644/4644), done.\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work", + "duration_seconds": 0.057441728000412695, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work", + "duration_seconds": 0.12273075699340552, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.0770113600010518, + "clone": 3.1568224640068365, + "diff": 0.01879543399263639, + "edit": 0.005346574995201081, + "fsck": 0.1757984619907802, + "read_search": 0.007632192995515652, + "status": 0.18019827299576718 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/agentfs-base/work", + "duration_seconds": 0.005131165991770104, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 4.322347755994997 + } + }, + "base_tree": { + "after": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "6f14043483396b35e1c0229a26c4cf79cfaa90f9f4dab5d95b30913ba7992f2c", + "symlinks": 0 + }, + "before": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "6f14043483396b35e1c0229a26c4cf79cfaa90f9f4dab5d95b30913ba7992f2c", + "symlinks": 0 + }, + "unchanged": true + }, + "benchmark": "phase7-git-workload", + "command": { + "agentfs_prefix": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-5f9c17332ed2467e865db70fa7e5dded", + "--no-default-allows", + "--" + ], + "argv": [ + "/home/ain3sh/factory/vfs/scripts/validation/git-workload-benchmark.py", + "--output", + "/home/ain3sh/factory/vfs/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-00.json", + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "workload_argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ] + }, + "correctness": { + "agentfs_backup_verify": true, + "agentfs_base_unchanged": true, + "agentfs_db_inspectable": true, + "agentfs_integrity_require_portable": true, + "agentfs_no_nonempty_sidecars": true, + "agentfs_portable": true, + "agentfs_returncode_zero": true, + "equivalence": { + "agentfs": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + }, + "checked": true, + "equivalent": true, + "native": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + } + }, + "native_returncode_zero": true, + "passed": true, + "performance_passed": false + }, + "database": { + "after": { + "artifacts": [ + { + "bytes": 57151488, + "path": "/tmp/agentfs-git-workload-85r78ug0/home/.agentfs/run/git-workload-5f9c17332ed2467e865db70fa7e5dded/delta.db" + } + ], + "path": "/tmp/agentfs-git-workload-85r78ug0/home/.agentfs/run/git-workload-5f9c17332ed2467e865db70fa7e5dded/delta.db", + "total_bytes": 57151488 + }, + "backup": { + "artifacts": { + "artifacts": [ + { + "bytes": 57151488, + "path": "/tmp/agentfs-git-workload-85r78ug0/git-workload-backup.db" + } + ], + "path": "/tmp/agentfs-git-workload-85r78ug0/git-workload-backup.db", + "total_bytes": 57151488 + }, + "inspect": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "path": "/tmp/agentfs-git-workload-85r78ug0/git-workload-backup.db", + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "backup", + "/tmp/agentfs-git-workload-85r78ug0/home/.agentfs/run/git-workload-5f9c17332ed2467e865db70fa7e5dded/delta.db", + "/tmp/agentfs-git-workload-85r78ug0/git-workload-backup.db", + "--verify" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0", + "duration_seconds": 2.705114637996303, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 241, + "stdout_tail": "Source: /tmp/agentfs-git-workload-85r78ug0/home/.agentfs/run/git-workload-5f9c17332ed2467e865db70fa7e5dded/delta.db\nBackup: /tmp/agentfs-git-workload-85r78ug0/git-workload-backup.db\nCheckpoint: complete\nCopy: complete\nVerification: complete\n", + "timed_out": false + } + }, + "inspect_after": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "integrity": { + "result": { + "checks": [ + { + "detail": "ok", + "name": "pragma.integrity_check", + "ok": true, + "violating_rows": null + }, + { + "detail": "present", + "name": "schema.table.fs_config", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_symlink", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.kv_store", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.tool_calls", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 0.5", + "name": "config.schema_version", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 65536", + "name": "config.chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 16384", + "name": "config.inline_threshold", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.kind_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_has_no_chunks", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunked_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_size_matches_blob", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.non_regular_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_reference_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunk_length_within_chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 1, expected 1", + "name": "namespace.root_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_is_directory", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_target_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_root_inode_has_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_names_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_directory_nlink_matches_dentries", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.directory_nlink_positive", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.rows_reference_symlink_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.inodes_have_rows", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable: no partial-origin rows", + "name": "overlay.portability_status", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable requirement satisfied", + "name": "overlay.require_portable", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_regular", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_sizes_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_paths_absolute", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_references_partial_origin", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_unique", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_index_in_range", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.whiteout_paths_absolute", + "ok": true, + "violating_rows": 0 + } + ], + "database": "/tmp/agentfs-git-workload-85r78ug0/home/.agentfs/run/git-workload-5f9c17332ed2467e865db70fa7e5dded/delta.db", + "ok": true, + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true + }, + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "integrity", + "/tmp/agentfs-git-workload-85r78ug0/home/.agentfs/run/git-workload-5f9c17332ed2467e865db70fa7e5dded/delta.db", + "--json", + "--require-portable" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0", + "duration_seconds": 2.660415284000919, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 6395, + "stdout_tail": "{\n \"database\": \"/tmp/agentfs-git-workload-85r78ug0/home/.agentfs/run/git-workload-5f9c17332ed2467e865db70fa7e5dded/delta.db\",\n \"ok\": true,\n \"portable\": true,\n \"origin_backed\": false,\n \"partial_origin_rows\": 0,\n \"checks\": [\n {\n \"name\": \"pragma.integrity_check\",\n \"ok\": true,\n \"detail\": \"ok\",\n \"violating_rows\": null\n },\n {\n \"name\": \"schema.table.fs_config\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_inode\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_dentry\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_data\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_symlink\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.kv_store\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.tool_calls\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.schema_version\",\n \"ok\": true,\n \"detail\": \"found 0.5\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.chunk_size\",\n \"ok\": true,\n \"detail\": \"found 65536\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.inline_threshold\",\n \"ok\": true,\n \"detail\": \"found 16384\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.kind_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_has_no_chunks\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunked_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_size_matches_blob\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.non_regular_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_reference_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunk_length_within_chunk_size\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.root_inode\",\n \"ok\": true,\n \"detail\": \"found 1, expected 1\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_is_directory\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_target_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_root_inode_has_dentry\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_names_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_directory_nlink_matches_dentries\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.directory_nlink_positive\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.rows_reference_symlink_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.inodes_have_rows\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.portability_status\",\n \"ok\": true,\n \"detail\": \"portable: no partial-origin rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.require_portable\",\n \"ok\": true,\n \"detail\": \"portable requirement satisfied\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_regular\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_sizes_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_references_partial_origin\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_unique\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_index_in_range\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.whiteout_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n }\n ]\n}\n", + "timed_out": false + } + }, + "nonempty_sidecars": false + }, + "environment": { + "AGENTFS_BIN": "/home/ain3sh/factory/vfs/target/release/agentfs", + "AGENTFS_PROFILE": "1" + }, + "git_commit": "ae1e13fcb1c52d5c57be051610a04aa9a318f2a5", + "kept_temp": false, + "native": { + "run": { + "argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/native", + "duration_seconds": 1.5439706809993368, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 15985, + "stdout_tail": "{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.4042654630029574, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/native/work\", \"duration_seconds\": 0.13711851699918043, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/native/work\", \"duration_seconds\": 0.15849535299639683, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/native/work\", \"duration_seconds\": 0.10858318299870007, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.0009379919938510284, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/native/work\", \"duration_seconds\": 0.19304880900017451, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/native/work\", \"duration_seconds\": 0.19995780500175897, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-85r78ug0/native/mirror.git\", \"/tmp/agentfs-git-workload-85r78ug0/native/work\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/native\", \"duration_seconds\": 0.38302744999236893, \"returncode\": 0, \"stderr_bytes\": 149, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-85r78ug0/native/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/native/work\", \"duration_seconds\": 0.14085585399880074, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/native/work\", \"duration_seconds\": 0.15976831800071523, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.20255771699885372, \"clone\": 0.3831163779977942, \"diff\": 0.4042654630029574, \"edit\": 0.0009379919938510284, \"fsck\": 0.19305972200527322, \"read_search\": 0.008362036998732947, \"status\": 0.30065786000341177}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-85r78ug0/native/work\", \"duration_seconds\": 0.0042254959989804775, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 1.4932545539923012}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.4042654630029574, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/native/work", + "duration_seconds": 0.13711851699918043, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/native/work", + "duration_seconds": 0.15849535299639683, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/native/work", + "duration_seconds": 0.10858318299870007, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.0009379919938510284, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/native/work", + "duration_seconds": 0.19304880900017451, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/native/work", + "duration_seconds": 0.19995780500175897, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-85r78ug0/native/mirror.git", + "/tmp/agentfs-git-workload-85r78ug0/native/work" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/native", + "duration_seconds": 0.38302744999236893, + "returncode": 0, + "stderr_bytes": 149, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-85r78ug0/native/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/native/work", + "duration_seconds": 0.14085585399880074, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/native/work", + "duration_seconds": 0.15976831800071523, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.20255771699885372, + "clone": 0.3831163779977942, + "diff": 0.4042654630029574, + "edit": 0.0009379919938510284, + "fsck": 0.19305972200527322, + "read_search": 0.008362036998732947, + "status": 0.30065786000341177 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-85r78ug0/native/work", + "duration_seconds": 0.0042254959989804775, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 1.4932545539923012 + } + }, + "parameters": { + "edit_files": 8, + "fixture_dirs": 8, + "fixture_file_size_bytes": 1024, + "fixture_files": 96, + "read_bytes": 4096, + "read_files": 64, + "search_token": "AGENTFS_TOKEN", + "skip_fsck": false, + "timeout_seconds": 180.0 + }, + "schema_version": 1, + "source": { + "kind": "source", + "mirror_head": "7d47056ea42636271ac020b86347fbbef49490aa", + "path": "/home/ain3sh/factory/vfs/.agents/benchmarks/fixtures/codex" + }, + "summary": { + "agentfs_base_unchanged": true, + "agentfs_seconds": 4.322347755994997, + "all_equivalent": true, + "correctness_passed": true, + "native_seconds": 1.4932545539923012, + "passed": true, + "performance_passed": false, + "phase_ratios": { + "checkout": { + "agentfs_seconds": 0.0770113600010518, + "native_seconds": 0.20255771699885372, + "ratio": 0.3801946484294529 + }, + "clone": { + "agentfs_seconds": 3.1568224640068365, + "native_seconds": 0.3831163779977942, + "ratio": 8.239852549516982 + }, + "diff": { + "agentfs_seconds": 0.01879543399263639, + "native_seconds": 0.4042654630029574, + "ratio": 0.04649280166804378 + }, + "edit": { + "agentfs_seconds": 0.005346574995201081, + "native_seconds": 0.0009379919938510284, + "ratio": 5.700021994057897 + }, + "fsck": { + "agentfs_seconds": 0.1757984619907802, + "native_seconds": 0.19305972200527322, + "ratio": 0.9105910863477699 + }, + "read_search": { + "agentfs_seconds": 0.007632192995515652, + "native_seconds": 0.008362036998732947, + "ratio": 0.912719352553943 + }, + "status": { + "agentfs_seconds": 0.18019827299576718, + "native_seconds": 0.30065786000341177, + "ratio": 0.5993466227482706 + } + }, + "ratio": 2.894582001735038, + "threshold_failures": [ + { + "agentfs_seconds": 3.1568224640068365, + "native_seconds": 0.3831163779977942, + "phase": "clone", + "ratio": 8.239852549516982 + }, + { + "agentfs_seconds": 0.005346574995201081, + "native_seconds": 0.0009379919938510284, + "phase": "edit", + "ratio": 5.700021994057897 + } + ] + }, + "temp_dir": "/tmp/agentfs-git-workload-85r78ug0" +} diff --git a/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-01.json b/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-01.json new file mode 100644 index 00000000..8cea49a8 --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-01.json @@ -0,0 +1,6642 @@ +{ + "agentfs": { + "bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "db_path": "/tmp/agentfs-git-workload-vrotv14m/home/.agentfs/run/git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8/delta.db", + "per_phase_counters": { + "checkpoint_count": 7, + "label_count": 7, + "labels_aligned": true, + "phases": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 34, + "agentfs_batcher_commit_latency_ns_total": 789697637, + "agentfs_batcher_commit_txns": 4778, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "base_fast_inode_invalidations": 15030, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create_count": 2, + "connection_reuse_count": 31200, + "connection_wait_count": 31202, + "connection_wait_nanos": 9124052, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 90, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5275, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21774, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 242741, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52468820, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5024, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11692, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 553447920, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 311550, + "fuse_op_getattr_count": 5024, + "fuse_op_getattr_nanos": 50461567, + "fuse_op_lookup_count": 11692, + "fuse_op_lookup_nanos": 124030072, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 112688262, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 22342144, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 931769, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1115546826, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 24419114, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16154, + "fuse_read_lane_wait_nanos": 1491247, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 31997, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673354, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1427498, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6769, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "phase": "clone", + "seq": 1 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 2, + "agentfs_batcher_commit_latency_ns_total": 5788108, + "agentfs_batcher_commit_txns": 9, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 5, + "agentfs_batcher_drains_timer": 3, + "agentfs_batcher_enqueues": 10, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 15, + "attr_cache_hits": 278, + "attr_cache_misses": 1815, + "base_fast_inode_invalidations": 51, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 15, + "connection_create_count": 4, + "connection_reuse_count": 627, + "connection_wait_count": 631, + "connection_wait_nanos": 309955, + "dentry_cache_hits": 73, + "dentry_cache_misses": 56, + "fuse_adapter_attr_hits": 4557, + "fuse_adapter_attr_misses": 733, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 35, + "fuse_adapter_inval_entry_notifications": 10, + "fuse_adapter_inval_inode_notifications": 28, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 12, + "fuse_adapter_negative_misses": 35, + "fuse_callback_count": 5360, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 9, + "fuse_dispatch_wait_count": 9, + "fuse_dispatch_wait_nanos": 93943, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 765456, + "fuse_flush_count": 10, + "fuse_flush_ranges": 10, + "fuse_getattr_count": 5290, + "fuse_ino_file_resolutions": 3, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 49, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 9, + "fuse_op_create_nanos": 3431766, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 9, + "fuse_op_forget_nanos": 127298, + "fuse_op_getattr_count": 5290, + "fuse_op_getattr_nanos": 19782915, + "fuse_op_lookup_count": 49, + "fuse_op_lookup_nanos": 1436231, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 15, + "fuse_op_other_nanos": 5954542, + "fuse_op_read_count": 8, + "fuse_op_read_nanos": 1237092, + "fuse_op_readdirplus_count": 4, + "fuse_op_readdirplus_nanos": 131536, + "fuse_op_setattr_count": 14, + "fuse_op_setattr_nanos": 6702010, + "fuse_op_write_count": 9, + "fuse_op_write_nanos": 271765, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 8, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 798, + "fuse_read_lane_wait_nanos": 99179, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 4, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5398, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 591393, + "fuse_write_count": 9, + "fuse_write_lane_wait_count": 42, + "fuse_write_lane_wait_nanos": 59610, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 1494, + "lookup_base_count": 0, + "lookup_count": 164, + "lookup_delta_count": 60, + "lookup_whiteout_count": 0, + "negative_cache_hits": 33, + "negative_cache_invalidations": 26, + "negative_cache_misses": 148, + "negative_lookup_count": 86, + "path_cache_hits": 73, + "path_cache_misses": 56, + "path_component_count": 124, + "path_resolution_count": 45, + "readdir_count": 0, + "readdir_plus_count": 2, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "checkout", + "seq": 2 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 4, + "agentfs_batcher_commit_latency_ns_total": 5136427, + "agentfs_batcher_commit_txns": 4, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 2, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 6, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 4, + "attr_cache_hits": 19, + "attr_cache_misses": 35, + "base_fast_inode_invalidations": 24, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 20, + "chunk_read_queries": 12, + "chunk_write_chunks": 20, + "connection_create_count": 0, + "connection_reuse_count": 1017, + "connection_wait_count": 1017, + "connection_wait_nanos": 315449, + "dentry_cache_hits": 20, + "dentry_cache_misses": 570, + "fuse_adapter_attr_hits": 1135, + "fuse_adapter_attr_misses": 7, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 289, + "fuse_adapter_inval_entry_notifications": 4, + "fuse_adapter_inval_inode_notifications": 8, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 289, + "fuse_callback_count": 2836, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 4, + "fuse_dispatch_wait_count": 4, + "fuse_dispatch_wait_nanos": 81301, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 1181458, + "fuse_flush_count": 6, + "fuse_flush_ranges": 6, + "fuse_getattr_count": 1142, + "fuse_ino_file_resolutions": 2, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 291, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 2, + "fuse_op_create_nanos": 889730, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 4, + "fuse_op_forget_nanos": 128620, + "fuse_op_getattr_count": 1142, + "fuse_op_getattr_nanos": 1841957, + "fuse_op_lookup_count": 291, + "fuse_op_lookup_nanos": 4757307, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 2762, + "fuse_op_other_nanos": 2688588, + "fuse_op_read_count": 12, + "fuse_op_read_nanos": 2255040, + "fuse_op_readdirplus_count": 1381, + "fuse_op_readdirplus_nanos": 51123272, + "fuse_op_setattr_count": 6, + "fuse_op_setattr_nanos": 9414591, + "fuse_op_write_count": 10, + "fuse_op_write_nanos": 1635933, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 12, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 996, + "fuse_read_lane_wait_nanos": 162288, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1381, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5606, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 1181458, + "fuse_write_count": 10, + "fuse_write_lane_wait_count": 14, + "fuse_write_lane_wait_nanos": 83233, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 26, + "lookup_base_count": 0, + "lookup_count": 598, + "lookup_delta_count": 295, + "lookup_whiteout_count": 0, + "negative_cache_hits": 4, + "negative_cache_invalidations": 4, + "negative_cache_misses": 873, + "negative_lookup_count": 578, + "path_cache_hits": 20, + "path_cache_misses": 570, + "path_component_count": 4606, + "path_resolution_count": 977, + "readdir_count": 0, + "readdir_plus_count": 688, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "status", + "seq": 3 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 1, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 8, + "connection_wait_count": 8, + "connection_wait_nanos": 5232, + "dentry_cache_hits": 1, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 69, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 1, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 1, + "fuse_callback_count": 76, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 69, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 1, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 69, + "fuse_op_getattr_nanos": 49216, + "fuse_op_lookup_count": 1, + "fuse_op_lookup_nanos": 29366, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 6, + "fuse_op_read_nanos": 913384, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 6, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 2, + "fuse_read_lane_wait_nanos": 1038, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 76, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 2, + "lookup_delta_count": 1, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 2, + "negative_lookup_count": 0, + "path_cache_hits": 1, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "read_search", + "seq": 4 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 1084922, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 8, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 8, + "attr_cache_hits": 0, + "attr_cache_misses": 24, + "base_fast_inode_invalidations": 16, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 32, + "connection_wait_count": 32, + "connection_wait_nanos": 11176, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 16, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 24, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 6398, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 16, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 16, + "fuse_op_getattr_nanos": 21876, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 8, + "fuse_op_other_nanos": 722175, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 8, + "fuse_op_setattr_nanos": 2011268, + "fuse_op_write_count": 8, + "fuse_op_write_nanos": 433906, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 8, + "fuse_read_lane_wait_nanos": 1100, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 40, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 6398, + "fuse_write_count": 8, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 1382, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 16, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 8, + "wal_checkpoint_nanos": 610916 + }, + "phase": "edit", + "seq": 5 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 1084, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 19, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 1103, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 1084, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 19, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 1084, + "fuse_op_getattr_nanos": 839969, + "fuse_op_lookup_count": 19, + "fuse_op_lookup_nanos": 58410, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 12, + "fuse_op_other_nanos": 8553, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 1115, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 19, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "diff", + "seq": 6 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 3, + "attr_cache_misses": 2, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 1, + "chunk_read_queries": 1, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 15, + "connection_wait_count": 15, + "connection_wait_nanos": 12525, + "dentry_cache_hits": 4, + "dentry_cache_misses": 2, + "fuse_adapter_attr_hits": 44, + "fuse_adapter_attr_misses": 1, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 4, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 239, + "fuse_adapter_negative_misses": 4, + "fuse_callback_count": 308, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 45, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 243, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 45, + "fuse_op_getattr_nanos": 80993, + "fuse_op_lookup_count": 243, + "fuse_op_lookup_nanos": 273303, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 36, + "fuse_op_other_nanos": 14113, + "fuse_op_read_count": 2, + "fuse_op_read_nanos": 310465, + "fuse_op_readdirplus_count": 18, + "fuse_op_readdirplus_nanos": 590522, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 2, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 15, + "fuse_read_lane_wait_nanos": 2952, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 18, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 344, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 2, + "lookup_base_count": 0, + "lookup_count": 8, + "lookup_delta_count": 4, + "lookup_whiteout_count": 0, + "negative_cache_hits": 239, + "negative_cache_invalidations": 0, + "negative_cache_misses": 9, + "negative_lookup_count": 2, + "path_cache_hits": 4, + "path_cache_misses": 2, + "path_component_count": 42, + "path_resolution_count": 10, + "readdir_count": 0, + "readdir_plus_count": 9, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "fsck", + "seq": 7 + } + ] + }, + "profile_counters": { + "last_by_source": { + "agentfs": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32902, + "connection_wait_count": 32908, + "connection_wait_nanos": 9784412, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1572509, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2918797 + }, + "fuse_session": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32902, + "connection_wait_count": 32908, + "connection_wait_nanos": 9784412, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1572509, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2918797 + }, + "phase-checkpoint-1": { + "agentfs_batcher_coalesced_ranges": 34, + "agentfs_batcher_commit_latency_ns_total": 789697637, + "agentfs_batcher_commit_txns": 4778, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "base_fast_inode_invalidations": 15030, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create_count": 2, + "connection_reuse_count": 31200, + "connection_wait_count": 31202, + "connection_wait_nanos": 9124052, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 90, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5275, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21774, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 242741, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52468820, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5024, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11692, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 553447920, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 311550, + "fuse_op_getattr_count": 5024, + "fuse_op_getattr_nanos": 50461567, + "fuse_op_lookup_count": 11692, + "fuse_op_lookup_nanos": 124030072, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 112688262, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 22342144, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 931769, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1115546826, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 24419114, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16154, + "fuse_read_lane_wait_nanos": 1491247, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 31997, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673354, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1427498, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6769, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "phase-checkpoint-2": { + "agentfs_batcher_coalesced_ranges": 36, + "agentfs_batcher_commit_latency_ns_total": 795485745, + "agentfs_batcher_commit_txns": 4787, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26200, + "attr_cache_misses": 30868, + "base_fast_inode_invalidations": 15081, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create_count": 6, + "connection_reuse_count": 31827, + "connection_wait_count": 31833, + "connection_wait_nanos": 9434007, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4647, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27134, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 336684, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10314, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11741, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 556879686, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 438848, + "fuse_op_getattr_count": 10314, + "fuse_op_getattr_nanos": 70244482, + "fuse_op_lookup_count": 11741, + "fuse_op_lookup_nanos": 125466303, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 118642804, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 23579236, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1063305, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1122248836, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 24690879, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16952, + "fuse_read_lane_wait_nanos": 1590426, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37395, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53264747, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1487108, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6802, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "phase-checkpoint-3": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 800622172, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26219, + "attr_cache_misses": 30903, + "base_fast_inode_invalidations": 15105, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32844, + "connection_wait_count": 32850, + "connection_wait_nanos": 9749456, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5782, + "fuse_adapter_attr_misses": 5674, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 29970, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11456, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12032, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 11456, + "fuse_op_getattr_nanos": 72086439, + "fuse_op_lookup_count": 12032, + "fuse_op_lookup_nanos": 130223610, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 121331392, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 25834276, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 52186577, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1131663427, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 26326812, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17948, + "fuse_read_lane_wait_nanos": 1752714, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43001, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446205, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1570341, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20856, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "phase-checkpoint-4": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 800622172, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26220, + "attr_cache_misses": 30903, + "base_fast_inode_invalidations": 15105, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32852, + "connection_wait_count": 32858, + "connection_wait_nanos": 9754688, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5851, + "fuse_adapter_attr_misses": 5674, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30046, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11525, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12033, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 11525, + "fuse_op_getattr_nanos": 72135655, + "fuse_op_lookup_count": 12033, + "fuse_op_lookup_nanos": 130252976, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 121331392, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 26747660, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 52186577, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1131663427, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 26326812, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 1753752, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43077, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446205, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1570341, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20856, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "phase-checkpoint-5": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26220, + "attr_cache_misses": 30927, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32884, + "connection_wait_count": 32890, + "connection_wait_nanos": 9765864, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5867, + "fuse_adapter_attr_misses": 5674, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30070, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11541, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12033, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 11541, + "fuse_op_getattr_nanos": 72157531, + "fuse_op_lookup_count": 12033, + "fuse_op_lookup_nanos": 130252976, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 122053567, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 26747660, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 52186577, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17958, + "fuse_read_lane_wait_nanos": 1754852, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43117, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1571723, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20872, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2773095 + }, + "phase-checkpoint-6": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26220, + "attr_cache_misses": 30927, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32884, + "connection_wait_count": 32890, + "connection_wait_nanos": 9765864, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6951, + "fuse_adapter_attr_misses": 5674, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5306, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31173, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12625, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12052, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12625, + "fuse_op_getattr_nanos": 72997500, + "fuse_op_lookup_count": 12052, + "fuse_op_lookup_nanos": 130311386, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 122062120, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 26747660, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 52186577, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17958, + "fuse_read_lane_wait_nanos": 1754852, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44232, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1571723, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20872, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6825, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2773095 + }, + "phase-checkpoint-7": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32899, + "connection_wait_count": 32905, + "connection_wait_nanos": 9778389, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1571723, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2773095 + }, + "run_parent": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32902, + "connection_wait_count": 32908, + "connection_wait_nanos": 9784412, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1572509, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2918797 + } + }, + "max_counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32902, + "connection_wait_count": 32908, + "connection_wait_nanos": 9784412, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1572509, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2918797 + }, + "summary_count": 10 + }, + "profile_enabled": true, + "profile_summary_count": 10, + "session": "git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8" + }, + "agentfs_overlay": { + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 34, + "agentfs_batcher_commit_latency_ns_total": 789697637, + "agentfs_batcher_commit_txns": 4778, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "base_fast_inode_invalidations": 15030, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create_count": 2, + "connection_reuse_count": 31200, + "connection_wait_count": 31202, + "connection_wait_nanos": 9124052, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 90, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5275, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21774, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 242741, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52468820, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5024, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11692, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 553447920, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 311550, + "fuse_op_getattr_count": 5024, + "fuse_op_getattr_nanos": 50461567, + "fuse_op_lookup_count": 11692, + "fuse_op_lookup_nanos": 124030072, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 112688262, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 22342144, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 931769, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1115546826, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 24419114, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16154, + "fuse_read_lane_wait_nanos": 1491247, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 31997, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673354, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1427498, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6769, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 36, + "agentfs_batcher_commit_latency_ns_total": 795485745, + "agentfs_batcher_commit_txns": 4787, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26200, + "attr_cache_misses": 30868, + "base_fast_inode_invalidations": 15081, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create_count": 6, + "connection_reuse_count": 31827, + "connection_wait_count": 31833, + "connection_wait_nanos": 9434007, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4647, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27134, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 336684, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10314, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11741, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 556879686, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 438848, + "fuse_op_getattr_count": 10314, + "fuse_op_getattr_nanos": 70244482, + "fuse_op_lookup_count": 11741, + "fuse_op_lookup_nanos": 125466303, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 118642804, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 23579236, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1063305, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1122248836, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 24690879, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16952, + "fuse_read_lane_wait_nanos": 1590426, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37395, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53264747, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1487108, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6802, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 800622172, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26219, + "attr_cache_misses": 30903, + "base_fast_inode_invalidations": 15105, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32844, + "connection_wait_count": 32850, + "connection_wait_nanos": 9749456, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5782, + "fuse_adapter_attr_misses": 5674, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 29970, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11456, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12032, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 11456, + "fuse_op_getattr_nanos": 72086439, + "fuse_op_lookup_count": 12032, + "fuse_op_lookup_nanos": 130223610, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 121331392, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 25834276, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 52186577, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1131663427, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 26326812, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17948, + "fuse_read_lane_wait_nanos": 1752714, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43001, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446205, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1570341, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20856, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 800622172, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26220, + "attr_cache_misses": 30903, + "base_fast_inode_invalidations": 15105, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32852, + "connection_wait_count": 32858, + "connection_wait_nanos": 9754688, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5851, + "fuse_adapter_attr_misses": 5674, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30046, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11525, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12033, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 11525, + "fuse_op_getattr_nanos": 72135655, + "fuse_op_lookup_count": 12033, + "fuse_op_lookup_nanos": 130252976, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 121331392, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 26747660, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 52186577, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1131663427, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 26326812, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 1753752, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43077, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446205, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1570341, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20856, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26220, + "attr_cache_misses": 30927, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32884, + "connection_wait_count": 32890, + "connection_wait_nanos": 9765864, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5867, + "fuse_adapter_attr_misses": 5674, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30070, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11541, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12033, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 11541, + "fuse_op_getattr_nanos": 72157531, + "fuse_op_lookup_count": 12033, + "fuse_op_lookup_nanos": 130252976, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 122053567, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 26747660, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 52186577, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17958, + "fuse_read_lane_wait_nanos": 1754852, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43117, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1571723, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20872, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2773095 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26220, + "attr_cache_misses": 30927, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32884, + "connection_wait_count": 32890, + "connection_wait_nanos": 9765864, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6951, + "fuse_adapter_attr_misses": 5674, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5306, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31173, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12625, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12052, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12625, + "fuse_op_getattr_nanos": 72997500, + "fuse_op_lookup_count": 12052, + "fuse_op_lookup_nanos": 130311386, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 122062120, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 26747660, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 52186577, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17958, + "fuse_read_lane_wait_nanos": 1754852, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44232, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1571723, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20872, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6825, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2773095 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32899, + "connection_wait_count": 32905, + "connection_wait_nanos": 9778389, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1571723, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2773095 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32902, + "connection_wait_count": 32908, + "connection_wait_nanos": 9784412, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1572509, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2918797 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "agentfs" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32902, + "connection_wait_count": 32908, + "connection_wait_nanos": 9784412, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1572509, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2918797 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "fuse_session" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32902, + "connection_wait_count": 32908, + "connection_wait_nanos": 9784412, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1572509, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2918797 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "run_parent" + } + ], + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8", + "--no-default-allows", + "--", + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/agentfs-base", + "duration_seconds": 4.07556226599263, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 34, + "agentfs_batcher_commit_latency_ns_total": 789697637, + "agentfs_batcher_commit_txns": 4778, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4681, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25922, + "attr_cache_misses": 29053, + "base_fast_inode_invalidations": 15030, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create_count": 2, + "connection_reuse_count": 31200, + "connection_wait_count": 31202, + "connection_wait_nanos": 9124052, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 90, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5275, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21774, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 242741, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52468820, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5024, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11692, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 553447920, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 311550, + "fuse_op_getattr_count": 5024, + "fuse_op_getattr_nanos": 50461567, + "fuse_op_lookup_count": 11692, + "fuse_op_lookup_nanos": 124030072, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 112688262, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 22342144, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 931769, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1115546826, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 24419114, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16154, + "fuse_read_lane_wait_nanos": 1491247, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 31997, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673354, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1427498, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19336, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6769, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 36, + "agentfs_batcher_commit_latency_ns_total": 795485745, + "agentfs_batcher_commit_txns": 4787, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4686, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26200, + "attr_cache_misses": 30868, + "base_fast_inode_invalidations": 15081, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create_count": 6, + "connection_reuse_count": 31827, + "connection_wait_count": 31833, + "connection_wait_nanos": 9434007, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4647, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27134, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 336684, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10314, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11741, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 556879686, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 438848, + "fuse_op_getattr_count": 10314, + "fuse_op_getattr_nanos": 70244482, + "fuse_op_lookup_count": 11741, + "fuse_op_lookup_nanos": 125466303, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 118642804, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 23579236, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1063305, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1122248836, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 24690879, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16952, + "fuse_read_lane_wait_nanos": 1590426, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37395, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53264747, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1487108, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20830, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6802, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 800622172, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26219, + "attr_cache_misses": 30903, + "base_fast_inode_invalidations": 15105, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32844, + "connection_wait_count": 32850, + "connection_wait_nanos": 9749456, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5782, + "fuse_adapter_attr_misses": 5674, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 29970, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11456, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12032, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 11456, + "fuse_op_getattr_nanos": 72086439, + "fuse_op_lookup_count": 12032, + "fuse_op_lookup_nanos": 130223610, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 121331392, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 25834276, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 52186577, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1131663427, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 26326812, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17948, + "fuse_read_lane_wait_nanos": 1752714, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43001, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446205, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1570341, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20856, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 800622172, + "agentfs_batcher_commit_txns": 4791, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26220, + "attr_cache_misses": 30903, + "base_fast_inode_invalidations": 15105, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32852, + "connection_wait_count": 32858, + "connection_wait_nanos": 9754688, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5851, + "fuse_adapter_attr_misses": 5674, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30046, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11525, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12033, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 11525, + "fuse_op_getattr_nanos": 72135655, + "fuse_op_lookup_count": 12033, + "fuse_op_lookup_nanos": 130252976, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 121331392, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 26747660, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 52186577, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1131663427, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 26326812, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17950, + "fuse_read_lane_wait_nanos": 1753752, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43077, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446205, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1570341, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20856, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2162179 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26220, + "attr_cache_misses": 30927, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32884, + "connection_wait_count": 32890, + "connection_wait_nanos": 9765864, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 5867, + "fuse_adapter_attr_misses": 5674, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30070, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11541, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12033, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 11541, + "fuse_op_getattr_nanos": 72157531, + "fuse_op_lookup_count": 12033, + "fuse_op_lookup_nanos": 130252976, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 122053567, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 26747660, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 52186577, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17958, + "fuse_read_lane_wait_nanos": 1754852, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43117, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1571723, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20872, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6806, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2773095 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26220, + "attr_cache_misses": 30927, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32884, + "connection_wait_count": 32890, + "connection_wait_nanos": 9765864, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6951, + "fuse_adapter_attr_misses": 5674, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5306, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31173, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12625, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12052, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12625, + "fuse_op_getattr_nanos": 72997500, + "fuse_op_lookup_count": 12052, + "fuse_op_lookup_nanos": 130311386, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 122062120, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 26747660, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 52186577, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17958, + "fuse_read_lane_wait_nanos": 1754852, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44232, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1571723, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20872, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6825, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2773095 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32899, + "connection_wait_count": 32905, + "connection_wait_nanos": 9778389, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1571723, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2773095 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32902, + "connection_wait_count": 32908, + "connection_wait_nanos": 9784412, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1572509, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2918797 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "agentfs" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32902, + "connection_wait_count": 32908, + "connection_wait_nanos": 9784412, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1572509, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2918797 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "fuse_session" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 801707094, + "agentfs_batcher_commit_txns": 4799, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4696, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1372160, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26223, + "attr_cache_misses": 30929, + "base_fast_inode_invalidations": 15121, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 6, + "connection_reuse_count": 32902, + "connection_wait_count": 32908, + "connection_wait_nanos": 9784412, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 6995, + "fuse_adapter_attr_misses": 5675, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5545, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31481, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 417985, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12670, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12295, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557769416, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 969, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 567468, + "fuse_op_getattr_count": 12670, + "fuse_op_getattr_nanos": 73078493, + "fuse_op_lookup_count": 12295, + "fuse_op_lookup_nanos": 130584689, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1225, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 122076233, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 27058125, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 52777099, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1133674695, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 26760718, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17973, + "fuse_read_lane_wait_nanos": 1757804, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44576, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452603, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1572509, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20874, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7064, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2918797 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "run_parent" + } + ], + "returncode": 0, + "stderr_bytes": 40565, + "stderr_tail": "atcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1372160,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26220,\"attr_cache_misses\":30927,\"base_fast_inode_invalidations\":15121,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":267,\"chunk_read_queries\":122,\"chunk_write_chunks\":1003,\"connection_create_count\":6,\"connection_reuse_count\":32884,\"connection_wait_count\":32890,\"connection_wait_nanos\":9765864,\"dentry_cache_hits\":26125,\"dentry_cache_misses\":14052,\"fuse_adapter_attr_hits\":6951,\"fuse_adapter_attr_misses\":5674,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6734,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5306,\"fuse_adapter_negative_misses\":6734,\"fuse_callback_count\":31173,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":52,\"fuse_dispatch_wait_count\":52,\"fuse_dispatch_wait_nanos\":417985,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":12625,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12052,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":557769416,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":969,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":567468,\"fuse_op_getattr_count\":12625,\"fuse_op_getattr_nanos\":72997500,\"fuse_op_lookup_count\":12052,\"fuse_op_lookup_nanos\":130311386,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1225,\"fuse_op_other_count\":3581,\"fuse_op_other_nanos\":122062120,\"fuse_op_read_count\":239,\"fuse_op_read_nanos\":26747660,\"fuse_op_readdirplus_count\":1407,\"fuse_op_readdirplus_nanos\":52186577,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1133674695,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":26760718,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":239,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17958,\"fuse_read_lane_wait_nanos\":1754852,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1407,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":44232,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452603,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1571723,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20872,\"lookup_base_count\":23,\"lookup_count\":41052,\"lookup_delta_count\":7547,\"lookup_whiteout_count\":0,\"negative_cache_hits\":6825,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46085,\"negative_lookup_count\":14920,\"path_cache_hits\":26125,\"path_cache_misses\":14052,\"path_component_count\":37632,\"path_resolution_count\":8140,\"readdir_count\":1,\"readdir_plus_count\":701,\"wal_checkpoint_count\":11,\"wal_checkpoint_nanos\":2773095},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"phase-checkpoint-6\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":40,\"agentfs_batcher_commit_latency_ns_total\":801707094,\"agentfs_batcher_commit_txns\":4799,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":18,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1372160,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26223,\"attr_cache_misses\":30929,\"base_fast_inode_invalidations\":15121,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create_count\":6,\"connection_reuse_count\":32899,\"connection_wait_count\":32905,\"connection_wait_nanos\":9778389,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":6995,\"fuse_adapter_attr_misses\":5675,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5545,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":31481,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":52,\"fuse_dispatch_wait_count\":52,\"fuse_dispatch_wait_nanos\":417985,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":12670,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12295,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":557769416,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":969,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":567468,\"fuse_op_getattr_count\":12670,\"fuse_op_getattr_nanos\":73078493,\"fuse_op_lookup_count\":12295,\"fuse_op_lookup_nanos\":130584689,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1225,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":122076233,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":27058125,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":52777099,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1133674695,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":26760718,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17973,\"fuse_read_lane_wait_nanos\":1757804,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":44576,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452603,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1571723,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20874,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7064,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":11,\"wal_checkpoint_nanos\":2773095},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"phase-checkpoint-7\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":40,\"agentfs_batcher_commit_latency_ns_total\":801707094,\"agentfs_batcher_commit_txns\":4799,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":18,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1372160,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26223,\"attr_cache_misses\":30929,\"base_fast_inode_invalidations\":15121,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create_count\":6,\"connection_reuse_count\":32902,\"connection_wait_count\":32908,\"connection_wait_nanos\":9784412,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":6995,\"fuse_adapter_attr_misses\":5675,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5545,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":31481,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":52,\"fuse_dispatch_wait_count\":52,\"fuse_dispatch_wait_nanos\":417985,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":12670,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12295,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":557769416,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":969,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":567468,\"fuse_op_getattr_count\":12670,\"fuse_op_getattr_nanos\":73078493,\"fuse_op_lookup_count\":12295,\"fuse_op_lookup_nanos\":130584689,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1225,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":122076233,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":27058125,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":52777099,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1133674695,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":26760718,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17973,\"fuse_read_lane_wait_nanos\":1757804,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":44576,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452603,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1572509,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20874,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7064,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":2918797},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"agentfs\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":40,\"agentfs_batcher_commit_latency_ns_total\":801707094,\"agentfs_batcher_commit_txns\":4799,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":18,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1372160,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26223,\"attr_cache_misses\":30929,\"base_fast_inode_invalidations\":15121,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create_count\":6,\"connection_reuse_count\":32902,\"connection_wait_count\":32908,\"connection_wait_nanos\":9784412,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":6995,\"fuse_adapter_attr_misses\":5675,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5545,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":31481,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":52,\"fuse_dispatch_wait_count\":52,\"fuse_dispatch_wait_nanos\":417985,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":12670,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12295,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":557769416,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":969,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":567468,\"fuse_op_getattr_count\":12670,\"fuse_op_getattr_nanos\":73078493,\"fuse_op_lookup_count\":12295,\"fuse_op_lookup_nanos\":130584689,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1225,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":122076233,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":27058125,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":52777099,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1133674695,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":26760718,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17973,\"fuse_read_lane_wait_nanos\":1757804,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":44576,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452603,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1572509,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20874,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7064,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":2918797},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"fuse_session\"}\n\nSession: git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8\n\nTo resume this session:\n agentfs run --session git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8\n\nTo see what changed:\n agentfs diff git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":40,\"agentfs_batcher_commit_latency_ns_total\":801707094,\"agentfs_batcher_commit_txns\":4799,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4696,\"agentfs_batcher_drains_timer\":18,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1372160,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26223,\"attr_cache_misses\":30929,\"base_fast_inode_invalidations\":15121,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create_count\":6,\"connection_reuse_count\":32902,\"connection_wait_count\":32908,\"connection_wait_nanos\":9784412,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":6995,\"fuse_adapter_attr_misses\":5675,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5545,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":31481,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":52,\"fuse_dispatch_wait_count\":52,\"fuse_dispatch_wait_nanos\":417985,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":12670,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12295,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":557769416,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":969,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":567468,\"fuse_op_getattr_count\":12670,\"fuse_op_getattr_nanos\":73078493,\"fuse_op_lookup_count\":12295,\"fuse_op_lookup_nanos\":130584689,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1225,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":122076233,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":27058125,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":52777099,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1133674695,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":26760718,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17973,\"fuse_read_lane_wait_nanos\":1757804,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":44576,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452603,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1572509,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20874,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7064,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":2918797},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"run_parent\"}\n", + "stdout_bytes": 18782, + "stdout_tail": "2026-07-03T11:39:04.371977Z INFO agentfs::fuser::session: resolved FUSE dispatch mode: parallel workers=7 queue_capacity=28\n2026-07-03T11:39:04.372283Z INFO agentfs::fuse: advertising FUSE_OVER_IO_URING\n2026-07-03T11:39:04.372445Z INFO agentfs::fuser::uring: starting fuse-over-io_uring queues nr_queues=14 depth=4\n2026-07-03T11:39:05.683375Z WARN agentfs::fuser::request: Request RequestId(27873): Failed to send reply: No such file or directory (os error 2)\n2026-07-03T11:39:06.683370Z WARN agentfs::fuser::request: Request RequestId(51197): Failed to send reply: No such file or directory (os error 2)\n{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.04466473500360735, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work\", \"duration_seconds\": 0.015283184999134392, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work\", \"duration_seconds\": 0.018319342998438515, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work\", \"duration_seconds\": 0.011019598998245783, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.004283816000679508, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work\", \"duration_seconds\": 0.18677330001082737, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work\", \"duration_seconds\": 0.0554803960112622, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-vrotv14m/agentfs-base/mirror.git\", \"/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/agentfs-base\", \"duration_seconds\": 2.8421052980120294, \"returncode\": 0, \"stderr_bytes\": 2208, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\nUpdating files: 41% (1915/4644)\\nUpdating files: 42% (1951/4644)\\nUpdating files: 43% (1997/4644)\\nUpdating files: 44% (2044/4644)\\nUpdating files: 45% (2090/4644)\\nUpdating files: 46% (2137/4644)\\nUpdating files: 47% (2183/4644)\\nUpdating files: 48% (2230/4644)\\nUpdating files: 49% (2276/4644)\\nUpdating files: 50% (2322/4644)\\nUpdating files: 51% (2369/4644)\\nUpdating files: 52% (2415/4644)\\nUpdating files: 53% (2462/4644)\\nUpdating files: 54% (2508/4644)\\nUpdating files: 55% (2555/4644)\\nUpdating files: 56% (2601/4644)\\nUpdating files: 57% (2648/4644)\\nUpdating files: 58% (2694/4644)\\nUpdating files: 59% (2740/4644)\\nUpdating files: 60% (2787/4644)\\nUpdating files: 61% (2833/4644)\\nUpdating files: 62% (2880/4644)\\nUpdating files: 63% (2926/4644)\\nUpdating files: 64% (2973/4644)\\nUpdating files: 65% (3019/4644)\\nUpdating files: 66% (3066/4644)\\nUpdating files: 67% (3112/4644)\\nUpdating files: 68% (3158/4644)\\nUpdating files: 69% (3205/4644)\\nUpdating files: 70% (3251/4644)\\nUpdating files: 71% (3298/4644)\\nUpdating files: 72% (3344/4644)\\nUpdating files: 73% (3391/4644)\\nUpdating files: 74% (3437/4644)\\nUpdating files: 75% (3483/4644)\\nUpdating files: 76% (3530/4644)\\nUpdating files: 77% (3576/4644)\\nUpdating files: 78% (3623/4644)\\nUpdating files: 79% (3669/4644)\\nUpdating files: 79% (3682/4644)\\nUpdating files: 80% (3716/4644)\\nUpdating files: 81% (3762/4644)\\nUpdating files: 82% (3809/4644)\\nUpdating files: 83% (3855/4644)\\nUpdating files: 84% (3901/4644)\\nUpdating files: 85% (3948/4644)\\nUpdating files: 86% (3994/4644)\\nUpdating files: 87% (4041/4644)\\nUpdating files: 88% (4087/4644)\\nUpdating files: 89% (4134/4644)\\nUpdating files: 90% (4180/4644)\\nUpdating files: 91% (4227/4644)\\nUpdating files: 92% (4273/4644)\\nUpdating files: 93% (4319/4644)\\nUpdating files: 94% (4366/4644)\\nUpdating files: 95% (4412/4644)\\nUpdating files: 96% (4459/4644)\\nUpdating files: 97% (4505/4644)\\nUpdating files: 98% (4552/4644)\\nUpdating files: 99% (4598/4644)\\nUpdating files: 100% (4644/4644)\\nUpdating files: 100% (4644/4644), done.\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work\", \"duration_seconds\": 0.03388290099974256, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work\", \"duration_seconds\": 0.11276068199367728, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.0579280270030722, \"clone\": 2.8421427930006757, \"diff\": 0.04466473500360735, \"edit\": 0.004283816000679508, \"fsck\": 0.18678362200444099, \"read_search\": 0.008317582993186079, \"status\": 0.14666951198887546}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work\", \"duration_seconds\": 0.005506264991709031, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 3.9917564260103973}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.04466473500360735, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work", + "duration_seconds": 0.015283184999134392, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work", + "duration_seconds": 0.018319342998438515, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work", + "duration_seconds": 0.011019598998245783, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.004283816000679508, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work", + "duration_seconds": 0.18677330001082737, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work", + "duration_seconds": 0.0554803960112622, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-vrotv14m/agentfs-base/mirror.git", + "/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/agentfs-base", + "duration_seconds": 2.8421052980120294, + "returncode": 0, + "stderr_bytes": 2208, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\nUpdating files: 41% (1915/4644)\nUpdating files: 42% (1951/4644)\nUpdating files: 43% (1997/4644)\nUpdating files: 44% (2044/4644)\nUpdating files: 45% (2090/4644)\nUpdating files: 46% (2137/4644)\nUpdating files: 47% (2183/4644)\nUpdating files: 48% (2230/4644)\nUpdating files: 49% (2276/4644)\nUpdating files: 50% (2322/4644)\nUpdating files: 51% (2369/4644)\nUpdating files: 52% (2415/4644)\nUpdating files: 53% (2462/4644)\nUpdating files: 54% (2508/4644)\nUpdating files: 55% (2555/4644)\nUpdating files: 56% (2601/4644)\nUpdating files: 57% (2648/4644)\nUpdating files: 58% (2694/4644)\nUpdating files: 59% (2740/4644)\nUpdating files: 60% (2787/4644)\nUpdating files: 61% (2833/4644)\nUpdating files: 62% (2880/4644)\nUpdating files: 63% (2926/4644)\nUpdating files: 64% (2973/4644)\nUpdating files: 65% (3019/4644)\nUpdating files: 66% (3066/4644)\nUpdating files: 67% (3112/4644)\nUpdating files: 68% (3158/4644)\nUpdating files: 69% (3205/4644)\nUpdating files: 70% (3251/4644)\nUpdating files: 71% (3298/4644)\nUpdating files: 72% (3344/4644)\nUpdating files: 73% (3391/4644)\nUpdating files: 74% (3437/4644)\nUpdating files: 75% (3483/4644)\nUpdating files: 76% (3530/4644)\nUpdating files: 77% (3576/4644)\nUpdating files: 78% (3623/4644)\nUpdating files: 79% (3669/4644)\nUpdating files: 79% (3682/4644)\nUpdating files: 80% (3716/4644)\nUpdating files: 81% (3762/4644)\nUpdating files: 82% (3809/4644)\nUpdating files: 83% (3855/4644)\nUpdating files: 84% (3901/4644)\nUpdating files: 85% (3948/4644)\nUpdating files: 86% (3994/4644)\nUpdating files: 87% (4041/4644)\nUpdating files: 88% (4087/4644)\nUpdating files: 89% (4134/4644)\nUpdating files: 90% (4180/4644)\nUpdating files: 91% (4227/4644)\nUpdating files: 92% (4273/4644)\nUpdating files: 93% (4319/4644)\nUpdating files: 94% (4366/4644)\nUpdating files: 95% (4412/4644)\nUpdating files: 96% (4459/4644)\nUpdating files: 97% (4505/4644)\nUpdating files: 98% (4552/4644)\nUpdating files: 99% (4598/4644)\nUpdating files: 100% (4644/4644)\nUpdating files: 100% (4644/4644), done.\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work", + "duration_seconds": 0.03388290099974256, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work", + "duration_seconds": 0.11276068199367728, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.0579280270030722, + "clone": 2.8421427930006757, + "diff": 0.04466473500360735, + "edit": 0.004283816000679508, + "fsck": 0.18678362200444099, + "read_search": 0.008317582993186079, + "status": 0.14666951198887546 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/agentfs-base/work", + "duration_seconds": 0.005506264991709031, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 3.9917564260103973 + } + }, + "base_tree": { + "after": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "824416d680afc4f637f6c91f409c57f34152034bf727d325b2ca14028b559c8a", + "symlinks": 0 + }, + "before": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "824416d680afc4f637f6c91f409c57f34152034bf727d325b2ca14028b559c8a", + "symlinks": 0 + }, + "unchanged": true + }, + "benchmark": "phase7-git-workload", + "command": { + "agentfs_prefix": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8", + "--no-default-allows", + "--" + ], + "argv": [ + "/home/ain3sh/factory/vfs/scripts/validation/git-workload-benchmark.py", + "--output", + "/home/ain3sh/factory/vfs/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-01.json", + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "workload_argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ] + }, + "correctness": { + "agentfs_backup_verify": true, + "agentfs_base_unchanged": true, + "agentfs_db_inspectable": true, + "agentfs_integrity_require_portable": true, + "agentfs_no_nonempty_sidecars": true, + "agentfs_portable": true, + "agentfs_returncode_zero": true, + "equivalence": { + "agentfs": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + }, + "checked": true, + "equivalent": true, + "native": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + } + }, + "native_returncode_zero": true, + "passed": true, + "performance_passed": false + }, + "database": { + "after": { + "artifacts": [ + { + "bytes": 57151488, + "path": "/tmp/agentfs-git-workload-vrotv14m/home/.agentfs/run/git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8/delta.db" + } + ], + "path": "/tmp/agentfs-git-workload-vrotv14m/home/.agentfs/run/git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8/delta.db", + "total_bytes": 57151488 + }, + "backup": { + "artifacts": { + "artifacts": [ + { + "bytes": 57151488, + "path": "/tmp/agentfs-git-workload-vrotv14m/git-workload-backup.db" + } + ], + "path": "/tmp/agentfs-git-workload-vrotv14m/git-workload-backup.db", + "total_bytes": 57151488 + }, + "inspect": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "path": "/tmp/agentfs-git-workload-vrotv14m/git-workload-backup.db", + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "backup", + "/tmp/agentfs-git-workload-vrotv14m/home/.agentfs/run/git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8/delta.db", + "/tmp/agentfs-git-workload-vrotv14m/git-workload-backup.db", + "--verify" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m", + "duration_seconds": 2.6307504769938532, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 241, + "stdout_tail": "Source: /tmp/agentfs-git-workload-vrotv14m/home/.agentfs/run/git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8/delta.db\nBackup: /tmp/agentfs-git-workload-vrotv14m/git-workload-backup.db\nCheckpoint: complete\nCopy: complete\nVerification: complete\n", + "timed_out": false + } + }, + "inspect_after": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "integrity": { + "result": { + "checks": [ + { + "detail": "ok", + "name": "pragma.integrity_check", + "ok": true, + "violating_rows": null + }, + { + "detail": "present", + "name": "schema.table.fs_config", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_symlink", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.kv_store", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.tool_calls", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 0.5", + "name": "config.schema_version", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 65536", + "name": "config.chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 16384", + "name": "config.inline_threshold", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.kind_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_has_no_chunks", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunked_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_size_matches_blob", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.non_regular_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_reference_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunk_length_within_chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 1, expected 1", + "name": "namespace.root_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_is_directory", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_target_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_root_inode_has_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_names_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_directory_nlink_matches_dentries", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.directory_nlink_positive", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.rows_reference_symlink_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.inodes_have_rows", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable: no partial-origin rows", + "name": "overlay.portability_status", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable requirement satisfied", + "name": "overlay.require_portable", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_regular", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_sizes_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_paths_absolute", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_references_partial_origin", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_unique", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_index_in_range", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.whiteout_paths_absolute", + "ok": true, + "violating_rows": 0 + } + ], + "database": "/tmp/agentfs-git-workload-vrotv14m/home/.agentfs/run/git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8/delta.db", + "ok": true, + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true + }, + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "integrity", + "/tmp/agentfs-git-workload-vrotv14m/home/.agentfs/run/git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8/delta.db", + "--json", + "--require-portable" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m", + "duration_seconds": 2.7116648530063685, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 6395, + "stdout_tail": "{\n \"database\": \"/tmp/agentfs-git-workload-vrotv14m/home/.agentfs/run/git-workload-da03f67d8f4c4f3a8fb1df80210c9bc8/delta.db\",\n \"ok\": true,\n \"portable\": true,\n \"origin_backed\": false,\n \"partial_origin_rows\": 0,\n \"checks\": [\n {\n \"name\": \"pragma.integrity_check\",\n \"ok\": true,\n \"detail\": \"ok\",\n \"violating_rows\": null\n },\n {\n \"name\": \"schema.table.fs_config\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_inode\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_dentry\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_data\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_symlink\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.kv_store\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.tool_calls\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.schema_version\",\n \"ok\": true,\n \"detail\": \"found 0.5\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.chunk_size\",\n \"ok\": true,\n \"detail\": \"found 65536\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.inline_threshold\",\n \"ok\": true,\n \"detail\": \"found 16384\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.kind_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_has_no_chunks\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunked_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_size_matches_blob\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.non_regular_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_reference_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunk_length_within_chunk_size\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.root_inode\",\n \"ok\": true,\n \"detail\": \"found 1, expected 1\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_is_directory\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_target_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_root_inode_has_dentry\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_names_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_directory_nlink_matches_dentries\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.directory_nlink_positive\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.rows_reference_symlink_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.inodes_have_rows\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.portability_status\",\n \"ok\": true,\n \"detail\": \"portable: no partial-origin rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.require_portable\",\n \"ok\": true,\n \"detail\": \"portable requirement satisfied\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_regular\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_sizes_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_references_partial_origin\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_unique\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_index_in_range\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.whiteout_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n }\n ]\n}\n", + "timed_out": false + } + }, + "nonempty_sidecars": false + }, + "environment": { + "AGENTFS_BIN": "/home/ain3sh/factory/vfs/target/release/agentfs", + "AGENTFS_PROFILE": "1" + }, + "git_commit": "ae1e13fcb1c52d5c57be051610a04aa9a318f2a5", + "kept_temp": false, + "native": { + "run": { + "argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/native", + "duration_seconds": 0.8084136260004016, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 15994, + "stdout_tail": "{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.014729614995303564, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/native/work\", \"duration_seconds\": 0.004802575000212528, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/native/work\", \"duration_seconds\": 0.00472492199332919, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/native/work\", \"duration_seconds\": 0.0051545699971029535, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.0005357149930205196, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/native/work\", \"duration_seconds\": 0.16168112799641676, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/native/work\", \"duration_seconds\": 0.17775318799249362, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-vrotv14m/native/mirror.git\", \"/tmp/agentfs-git-workload-vrotv14m/native/work\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/native\", \"duration_seconds\": 0.36050010100007057, \"returncode\": 0, \"stderr_bytes\": 149, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-vrotv14m/native/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/native/work\", \"duration_seconds\": 0.008400777995120734, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/native/work\", \"duration_seconds\": 0.009163744995021261, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.1803960039978847, \"clone\": 0.3605561690055765, \"diff\": 0.014729614995303564, \"edit\": 0.0005357149930205196, \"fsck\": 0.16169250100210775, \"read_search\": 0.006577431006007828, \"status\": 0.017589443988981657}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-vrotv14m/native/work\", \"duration_seconds\": 0.0039573130052303895, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 0.7422934740025084}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.014729614995303564, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/native/work", + "duration_seconds": 0.004802575000212528, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/native/work", + "duration_seconds": 0.00472492199332919, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/native/work", + "duration_seconds": 0.0051545699971029535, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.0005357149930205196, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/native/work", + "duration_seconds": 0.16168112799641676, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/native/work", + "duration_seconds": 0.17775318799249362, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-vrotv14m/native/mirror.git", + "/tmp/agentfs-git-workload-vrotv14m/native/work" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/native", + "duration_seconds": 0.36050010100007057, + "returncode": 0, + "stderr_bytes": 149, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-vrotv14m/native/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/native/work", + "duration_seconds": 0.008400777995120734, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/native/work", + "duration_seconds": 0.009163744995021261, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.1803960039978847, + "clone": 0.3605561690055765, + "diff": 0.014729614995303564, + "edit": 0.0005357149930205196, + "fsck": 0.16169250100210775, + "read_search": 0.006577431006007828, + "status": 0.017589443988981657 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-vrotv14m/native/work", + "duration_seconds": 0.0039573130052303895, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 0.7422934740025084 + } + }, + "parameters": { + "edit_files": 8, + "fixture_dirs": 8, + "fixture_file_size_bytes": 1024, + "fixture_files": 96, + "read_bytes": 4096, + "read_files": 64, + "search_token": "AGENTFS_TOKEN", + "skip_fsck": false, + "timeout_seconds": 180.0 + }, + "schema_version": 1, + "source": { + "kind": "source", + "mirror_head": "7d47056ea42636271ac020b86347fbbef49490aa", + "path": "/home/ain3sh/factory/vfs/.agents/benchmarks/fixtures/codex" + }, + "summary": { + "agentfs_base_unchanged": true, + "agentfs_seconds": 3.9917564260103973, + "all_equivalent": true, + "correctness_passed": true, + "native_seconds": 0.7422934740025084, + "passed": true, + "performance_passed": false, + "phase_ratios": { + "checkout": { + "agentfs_seconds": 0.0579280270030722, + "native_seconds": 0.1803960039978847, + "ratio": 0.32111591010492374 + }, + "clone": { + "agentfs_seconds": 2.8421427930006757, + "native_seconds": 0.3605561690055765, + "ratio": 7.882663055909932 + }, + "diff": { + "agentfs_seconds": 0.04466473500360735, + "native_seconds": 0.014729614995303564, + "ratio": 3.032308381301778 + }, + "edit": { + "agentfs_seconds": 0.004283816000679508, + "native_seconds": 0.0005357149930205196, + "ratio": 7.996445976854383 + }, + "fsck": { + "agentfs_seconds": 0.18678362200444099, + "native_seconds": 0.16169250100210775, + "ratio": 1.1551780128752303 + }, + "read_search": { + "agentfs_seconds": 0.008317582993186079, + "native_seconds": 0.006577431006007828, + "ratio": 1.2645640806553191 + }, + "status": { + "agentfs_seconds": 0.14666951198887546, + "native_seconds": 0.017589443988981657, + "ratio": 8.33849620719972 + } + }, + "ratio": 5.377598706999959, + "threshold_failures": [ + { + "agentfs_seconds": 2.8421427930006757, + "native_seconds": 0.3605561690055765, + "phase": "clone", + "ratio": 7.882663055909932 + }, + { + "agentfs_seconds": 0.04466473500360735, + "native_seconds": 0.014729614995303564, + "phase": "diff", + "ratio": 3.032308381301778 + }, + { + "agentfs_seconds": 0.004283816000679508, + "native_seconds": 0.0005357149930205196, + "phase": "edit", + "ratio": 7.996445976854383 + }, + { + "agentfs_seconds": 0.14666951198887546, + "native_seconds": 0.017589443988981657, + "phase": "status", + "ratio": 8.33849620719972 + } + ] + }, + "temp_dir": "/tmp/agentfs-git-workload-vrotv14m" +} diff --git a/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-02.json b/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-02.json new file mode 100644 index 00000000..d7a396fd --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-02.json @@ -0,0 +1,6630 @@ +{ + "agentfs": { + "bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "db_path": "/tmp/agentfs-git-workload-j7ifkggm/home/.agentfs/run/git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33/delta.db", + "per_phase_counters": { + "checkpoint_count": 7, + "label_count": 7, + "labels_aligned": true, + "phases": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 780091371, + "agentfs_batcher_commit_txns": 4769, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4683, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9386, + "attr_cache_hits": 25922, + "attr_cache_misses": 29058, + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 969, + "connection_create_count": 2, + "connection_reuse_count": 31194, + "connection_wait_count": 31196, + "connection_wait_nanos": 9065389, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 106, + "fuse_adapter_attr_misses": 4935, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5273, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21789, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 36, + "fuse_dispatch_wait_count": 36, + "fuse_dispatch_wait_nanos": 273079, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5041, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11690, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 554746652, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 516055, + "fuse_op_getattr_count": 5041, + "fuse_op_getattr_nanos": 49888676, + "fuse_op_lookup_count": 11690, + "fuse_op_lookup_nanos": 121710720, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 97296284, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26582634, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 819870, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1105691655, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 26568776, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16158, + "fuse_read_lane_wait_nanos": 1426341, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32013, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673123, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1543871, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19340, + "lookup_base_count": 23, + "lookup_count": 40292, + "lookup_delta_count": 7193, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6769, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14260, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32908, + "path_resolution_count": 7120, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "phase": "clone", + "seq": 1 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 2, + "agentfs_batcher_commit_latency_ns_total": 3300928, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 5, + "agentfs_batcher_drains_timer": 2, + "agentfs_batcher_enqueues": 9, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 1, + "agentfs_batcher_txn_inodes_total": 14, + "attr_cache_hits": 234, + "attr_cache_misses": 1764, + "base_fast_inode_invalidations": 51, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 12, + "connection_create_count": 7, + "connection_reuse_count": 620, + "connection_wait_count": 627, + "connection_wait_nanos": 193903, + "dentry_cache_hits": 73, + "dentry_cache_misses": 56, + "fuse_adapter_attr_hits": 4588, + "fuse_adapter_attr_misses": 687, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 35, + "fuse_adapter_inval_entry_notifications": 10, + "fuse_adapter_inval_inode_notifications": 28, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 13, + "fuse_adapter_negative_misses": 35, + "fuse_callback_count": 5346, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 5, + "fuse_dispatch_parallel_tasks": 8, + "fuse_dispatch_wait_count": 8, + "fuse_dispatch_wait_nanos": 69037, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 694731, + "fuse_flush_count": 9, + "fuse_flush_ranges": 9, + "fuse_getattr_count": 5275, + "fuse_ino_file_resolutions": 3, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 50, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 9, + "fuse_op_create_nanos": 1537081, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 8, + "fuse_op_forget_nanos": 64349, + "fuse_op_getattr_count": 5275, + "fuse_op_getattr_nanos": 14643186, + "fuse_op_lookup_count": 50, + "fuse_op_lookup_nanos": 725292, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 15, + "fuse_op_other_nanos": 3072508, + "fuse_op_read_count": 8, + "fuse_op_read_nanos": 941772, + "fuse_op_readdirplus_count": 4, + "fuse_op_readdirplus_nanos": 109460, + "fuse_op_setattr_count": 14, + "fuse_op_setattr_nanos": 5101169, + "fuse_op_write_count": 9, + "fuse_op_write_nanos": 188263, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 8, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 752, + "fuse_read_lane_wait_nanos": 86525, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 4, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5384, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 591393, + "fuse_write_count": 9, + "fuse_write_lane_wait_count": 42, + "fuse_write_lane_wait_nanos": 28958, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 1402, + "lookup_base_count": 0, + "lookup_count": 164, + "lookup_delta_count": 60, + "lookup_whiteout_count": 0, + "negative_cache_hits": 34, + "negative_cache_invalidations": 26, + "negative_cache_misses": 148, + "negative_lookup_count": 86, + "path_cache_hits": 73, + "path_cache_misses": 56, + "path_component_count": 124, + "path_resolution_count": 45, + "readdir_count": 0, + "readdir_plus_count": 2, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "checkout", + "seq": 2 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 4, + "agentfs_batcher_commit_latency_ns_total": 5060016, + "agentfs_batcher_commit_txns": 4, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 2, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 6, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 4, + "attr_cache_hits": 20, + "attr_cache_misses": 36, + "base_fast_inode_invalidations": 24, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 20, + "chunk_read_queries": 12, + "chunk_write_chunks": 20, + "connection_create_count": 0, + "connection_reuse_count": 1017, + "connection_wait_count": 1017, + "connection_wait_nanos": 301340, + "dentry_cache_hits": 20, + "dentry_cache_misses": 570, + "fuse_adapter_attr_hits": 1427, + "fuse_adapter_attr_misses": 8, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 289, + "fuse_adapter_inval_entry_notifications": 4, + "fuse_adapter_inval_inode_notifications": 8, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 1, + "fuse_adapter_negative_misses": 289, + "fuse_callback_count": 3130, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 3, + "fuse_dispatch_wait_count": 3, + "fuse_dispatch_wait_nanos": 59641, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 1181458, + "fuse_flush_count": 6, + "fuse_flush_ranges": 6, + "fuse_getattr_count": 1435, + "fuse_ino_file_resolutions": 2, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 292, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 2, + "fuse_op_create_nanos": 1056740, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 3, + "fuse_op_forget_nanos": 45851, + "fuse_op_getattr_count": 1435, + "fuse_op_getattr_nanos": 1330804, + "fuse_op_lookup_count": 292, + "fuse_op_lookup_nanos": 4541886, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 2762, + "fuse_op_other_nanos": 3231893, + "fuse_op_read_count": 12, + "fuse_op_read_nanos": 2513384, + "fuse_op_readdirplus_count": 1381, + "fuse_op_readdirplus_nanos": 46560584, + "fuse_op_setattr_count": 6, + "fuse_op_setattr_nanos": 9561595, + "fuse_op_write_count": 10, + "fuse_op_write_nanos": 2257162, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 12, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 997, + "fuse_read_lane_wait_nanos": 91161, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1381, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5900, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 1181458, + "fuse_write_count": 10, + "fuse_write_lane_wait_count": 14, + "fuse_write_lane_wait_nanos": 4293, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 28, + "lookup_base_count": 0, + "lookup_count": 598, + "lookup_delta_count": 295, + "lookup_whiteout_count": 0, + "negative_cache_hits": 5, + "negative_cache_invalidations": 4, + "negative_cache_misses": 873, + "negative_lookup_count": 578, + "path_cache_hits": 20, + "path_cache_misses": 570, + "path_component_count": 4606, + "path_resolution_count": 977, + "readdir_count": 0, + "readdir_plus_count": 688, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "status", + "seq": 3 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 1, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 8, + "connection_wait_count": 8, + "connection_wait_nanos": 4329, + "dentry_cache_hits": 1, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 69, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 1, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 1, + "fuse_callback_count": 76, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 69, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 1, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 69, + "fuse_op_getattr_nanos": 55564, + "fuse_op_lookup_count": 1, + "fuse_op_lookup_nanos": 24464, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 6, + "fuse_op_read_nanos": 670168, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 6, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 2, + "fuse_read_lane_wait_nanos": 510, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 76, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 2, + "lookup_delta_count": 1, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 2, + "negative_lookup_count": 0, + "path_cache_hits": 1, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "read_search", + "seq": 4 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 2108523, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 8, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 8, + "attr_cache_hits": 0, + "attr_cache_misses": 24, + "base_fast_inode_invalidations": 16, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 32, + "connection_wait_count": 32, + "connection_wait_nanos": 25344, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 16, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 24, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 6398, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 16, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 16, + "fuse_op_getattr_nanos": 34846, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 8, + "fuse_op_other_nanos": 1824743, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 8, + "fuse_op_setattr_nanos": 4153070, + "fuse_op_write_count": 8, + "fuse_op_write_nanos": 924266, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 8, + "fuse_read_lane_wait_nanos": 2488, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 40, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 6398, + "fuse_write_count": 8, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 2932, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 16, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 8, + "wal_checkpoint_nanos": 1577905 + }, + "phase": "edit", + "seq": 5 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 2008, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 93, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 2101, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 2008, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 93, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 2008, + "fuse_op_getattr_nanos": 1470257, + "fuse_op_lookup_count": 93, + "fuse_op_lookup_nanos": 121829, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 12, + "fuse_op_other_nanos": 8830, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 2113, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 93, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "diff", + "seq": 6 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 3, + "attr_cache_misses": 2, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 1, + "chunk_read_queries": 1, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 15, + "connection_wait_count": 15, + "connection_wait_nanos": 16644, + "dentry_cache_hits": 4, + "dentry_cache_misses": 2, + "fuse_adapter_attr_hits": 44, + "fuse_adapter_attr_misses": 1, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 4, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 243, + "fuse_adapter_negative_misses": 4, + "fuse_callback_count": 312, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 45, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 247, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 45, + "fuse_op_getattr_nanos": 95759, + "fuse_op_lookup_count": 247, + "fuse_op_lookup_nanos": 348006, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 36, + "fuse_op_other_nanos": 17940, + "fuse_op_read_count": 2, + "fuse_op_read_nanos": 348286, + "fuse_op_readdirplus_count": 18, + "fuse_op_readdirplus_nanos": 762484, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 2, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 15, + "fuse_read_lane_wait_nanos": 4298, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 18, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 348, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 2, + "lookup_base_count": 0, + "lookup_count": 8, + "lookup_delta_count": 4, + "lookup_whiteout_count": 0, + "negative_cache_hits": 243, + "negative_cache_invalidations": 0, + "negative_cache_misses": 9, + "negative_lookup_count": 2, + "path_cache_hits": 4, + "path_cache_misses": 2, + "path_component_count": 42, + "path_resolution_count": 10, + "readdir_count": 0, + "readdir_plus_count": 9, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "fsck", + "seq": 7 + } + ] + }, + "profile_counters": { + "last_by_source": { + "agentfs": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32889, + "connection_wait_count": 32898, + "connection_wait_nanos": 9613574, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1581400, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2249063 + }, + "fuse_session": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32889, + "connection_wait_count": 32898, + "connection_wait_nanos": 9613574, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1581400, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2249063 + }, + "phase-checkpoint-1": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 780091371, + "agentfs_batcher_commit_txns": 4769, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4683, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9386, + "attr_cache_hits": 25922, + "attr_cache_misses": 29058, + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 969, + "connection_create_count": 2, + "connection_reuse_count": 31194, + "connection_wait_count": 31196, + "connection_wait_nanos": 9065389, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 106, + "fuse_adapter_attr_misses": 4935, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5273, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21789, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 36, + "fuse_dispatch_wait_count": 36, + "fuse_dispatch_wait_nanos": 273079, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5041, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11690, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 554746652, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 516055, + "fuse_op_getattr_count": 5041, + "fuse_op_getattr_nanos": 49888676, + "fuse_op_lookup_count": 11690, + "fuse_op_lookup_nanos": 121710720, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 97296284, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26582634, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 819870, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1105691655, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 26568776, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16158, + "fuse_read_lane_wait_nanos": 1426341, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32013, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673123, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1543871, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19340, + "lookup_base_count": 23, + "lookup_count": 40292, + "lookup_delta_count": 7193, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6769, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14260, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32908, + "path_resolution_count": 7120, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "phase-checkpoint-2": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 783392299, + "agentfs_batcher_commit_txns": 4777, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26156, + "attr_cache_misses": 30822, + "base_fast_inode_invalidations": 15082, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 981, + "connection_create_count": 9, + "connection_reuse_count": 31814, + "connection_wait_count": 31823, + "connection_wait_nanos": 9259292, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4694, + "fuse_adapter_attr_misses": 5622, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5286, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27135, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 342116, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10316, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11740, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 556283733, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 41, + "fuse_op_forget_nanos": 580404, + "fuse_op_getattr_count": 10316, + "fuse_op_getattr_nanos": 64531862, + "fuse_op_lookup_count": 11740, + "fuse_op_lookup_nanos": 122436012, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 100368792, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 27524406, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 929330, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1110792824, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 26757039, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16910, + "fuse_read_lane_wait_nanos": 1512866, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37397, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53264516, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1572829, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20742, + "lookup_base_count": 23, + "lookup_count": 40456, + "lookup_delta_count": 7253, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6803, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14346, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33032, + "path_resolution_count": 7165, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "phase-checkpoint-3": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 788452315, + "agentfs_batcher_commit_txns": 4781, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4690, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26176, + "attr_cache_misses": 30858, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32831, + "connection_wait_count": 32840, + "connection_wait_nanos": 9560632, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6121, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30265, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11751, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12032, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 11751, + "fuse_op_getattr_nanos": 65862666, + "fuse_op_lookup_count": 12032, + "fuse_op_lookup_nanos": 126977898, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 103600685, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 30037790, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 47489914, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1120354419, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 29014201, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17907, + "fuse_read_lane_wait_nanos": 1604027, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43297, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54445974, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1577122, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20770, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6808, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14924, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "phase-checkpoint-4": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 788452315, + "agentfs_batcher_commit_txns": 4781, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4690, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26177, + "attr_cache_misses": 30858, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32839, + "connection_wait_count": 32848, + "connection_wait_nanos": 9564961, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6190, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30341, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11820, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12033, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 11820, + "fuse_op_getattr_nanos": 65918230, + "fuse_op_lookup_count": 12033, + "fuse_op_lookup_nanos": 127002362, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 103600685, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30707958, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 47489914, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1120354419, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 29014201, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17909, + "fuse_read_lane_wait_nanos": 1604537, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43373, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54445974, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1577122, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20770, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6808, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "phase-checkpoint-5": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26177, + "attr_cache_misses": 30882, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32871, + "connection_wait_count": 32880, + "connection_wait_nanos": 9590305, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6206, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30365, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11836, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12033, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 11836, + "fuse_op_getattr_nanos": 65953076, + "fuse_op_lookup_count": 12033, + "fuse_op_lookup_nanos": 127002362, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 105425428, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30707958, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 47489914, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17917, + "fuse_read_lane_wait_nanos": 1607025, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43413, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1580054, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20786, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6808, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2103531 + }, + "phase-checkpoint-6": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26177, + "attr_cache_misses": 30882, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32871, + "connection_wait_count": 32880, + "connection_wait_nanos": 9590305, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 8214, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5380, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32466, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13844, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12126, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13844, + "fuse_op_getattr_nanos": 67423333, + "fuse_op_lookup_count": 12126, + "fuse_op_lookup_nanos": 127124191, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 105434258, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30707958, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 47489914, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17917, + "fuse_read_lane_wait_nanos": 1607025, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45526, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1580054, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20786, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6901, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2103531 + }, + "phase-checkpoint-7": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32886, + "connection_wait_count": 32895, + "connection_wait_nanos": 9606949, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1580054, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2103531 + }, + "run_parent": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32889, + "connection_wait_count": 32898, + "connection_wait_nanos": 9613574, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1581400, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2249063 + } + }, + "max_counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32889, + "connection_wait_count": 32898, + "connection_wait_nanos": 9613574, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1581400, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2249063 + }, + "summary_count": 10 + }, + "profile_enabled": true, + "profile_summary_count": 10, + "session": "git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33" + }, + "agentfs_overlay": { + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 780091371, + "agentfs_batcher_commit_txns": 4769, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4683, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9386, + "attr_cache_hits": 25922, + "attr_cache_misses": 29058, + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 969, + "connection_create_count": 2, + "connection_reuse_count": 31194, + "connection_wait_count": 31196, + "connection_wait_nanos": 9065389, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 106, + "fuse_adapter_attr_misses": 4935, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5273, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21789, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 36, + "fuse_dispatch_wait_count": 36, + "fuse_dispatch_wait_nanos": 273079, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5041, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11690, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 554746652, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 516055, + "fuse_op_getattr_count": 5041, + "fuse_op_getattr_nanos": 49888676, + "fuse_op_lookup_count": 11690, + "fuse_op_lookup_nanos": 121710720, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 97296284, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26582634, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 819870, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1105691655, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 26568776, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16158, + "fuse_read_lane_wait_nanos": 1426341, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32013, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673123, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1543871, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19340, + "lookup_base_count": 23, + "lookup_count": 40292, + "lookup_delta_count": 7193, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6769, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14260, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32908, + "path_resolution_count": 7120, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 783392299, + "agentfs_batcher_commit_txns": 4777, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26156, + "attr_cache_misses": 30822, + "base_fast_inode_invalidations": 15082, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 981, + "connection_create_count": 9, + "connection_reuse_count": 31814, + "connection_wait_count": 31823, + "connection_wait_nanos": 9259292, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4694, + "fuse_adapter_attr_misses": 5622, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5286, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27135, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 342116, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10316, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11740, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 556283733, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 41, + "fuse_op_forget_nanos": 580404, + "fuse_op_getattr_count": 10316, + "fuse_op_getattr_nanos": 64531862, + "fuse_op_lookup_count": 11740, + "fuse_op_lookup_nanos": 122436012, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 100368792, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 27524406, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 929330, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1110792824, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 26757039, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16910, + "fuse_read_lane_wait_nanos": 1512866, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37397, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53264516, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1572829, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20742, + "lookup_base_count": 23, + "lookup_count": 40456, + "lookup_delta_count": 7253, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6803, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14346, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33032, + "path_resolution_count": 7165, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 788452315, + "agentfs_batcher_commit_txns": 4781, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4690, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26176, + "attr_cache_misses": 30858, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32831, + "connection_wait_count": 32840, + "connection_wait_nanos": 9560632, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6121, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30265, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11751, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12032, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 11751, + "fuse_op_getattr_nanos": 65862666, + "fuse_op_lookup_count": 12032, + "fuse_op_lookup_nanos": 126977898, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 103600685, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 30037790, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 47489914, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1120354419, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 29014201, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17907, + "fuse_read_lane_wait_nanos": 1604027, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43297, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54445974, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1577122, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20770, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6808, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14924, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 788452315, + "agentfs_batcher_commit_txns": 4781, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4690, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26177, + "attr_cache_misses": 30858, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32839, + "connection_wait_count": 32848, + "connection_wait_nanos": 9564961, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6190, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30341, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11820, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12033, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 11820, + "fuse_op_getattr_nanos": 65918230, + "fuse_op_lookup_count": 12033, + "fuse_op_lookup_nanos": 127002362, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 103600685, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30707958, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 47489914, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1120354419, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 29014201, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17909, + "fuse_read_lane_wait_nanos": 1604537, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43373, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54445974, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1577122, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20770, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6808, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26177, + "attr_cache_misses": 30882, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32871, + "connection_wait_count": 32880, + "connection_wait_nanos": 9590305, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6206, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30365, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11836, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12033, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 11836, + "fuse_op_getattr_nanos": 65953076, + "fuse_op_lookup_count": 12033, + "fuse_op_lookup_nanos": 127002362, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 105425428, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30707958, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 47489914, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17917, + "fuse_read_lane_wait_nanos": 1607025, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43413, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1580054, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20786, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6808, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2103531 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26177, + "attr_cache_misses": 30882, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32871, + "connection_wait_count": 32880, + "connection_wait_nanos": 9590305, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 8214, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5380, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32466, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13844, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12126, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13844, + "fuse_op_getattr_nanos": 67423333, + "fuse_op_lookup_count": 12126, + "fuse_op_lookup_nanos": 127124191, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 105434258, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30707958, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 47489914, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17917, + "fuse_read_lane_wait_nanos": 1607025, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45526, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1580054, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20786, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6901, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2103531 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32886, + "connection_wait_count": 32895, + "connection_wait_nanos": 9606949, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1580054, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2103531 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32889, + "connection_wait_count": 32898, + "connection_wait_nanos": 9613574, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1581400, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2249063 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "agentfs" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32889, + "connection_wait_count": 32898, + "connection_wait_nanos": 9613574, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1581400, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2249063 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "fuse_session" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32889, + "connection_wait_count": 32898, + "connection_wait_nanos": 9613574, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1581400, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2249063 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "run_parent" + } + ], + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33", + "--no-default-allows", + "--", + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/agentfs-base", + "duration_seconds": 4.073011571003008, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 780091371, + "agentfs_batcher_commit_txns": 4769, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4683, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4731, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 3, + "agentfs_batcher_txn_inodes_total": 9386, + "attr_cache_hits": 25922, + "attr_cache_misses": 29058, + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 969, + "connection_create_count": 2, + "connection_reuse_count": 31194, + "connection_wait_count": 31196, + "connection_wait_nanos": 9065389, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 106, + "fuse_adapter_attr_misses": 4935, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5273, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21789, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 36, + "fuse_dispatch_wait_count": 36, + "fuse_dispatch_wait_nanos": 273079, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4731, + "fuse_flush_ranges": 4731, + "fuse_getattr_count": 5041, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11690, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 554746652, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 33, + "fuse_op_forget_nanos": 516055, + "fuse_op_getattr_count": 5041, + "fuse_op_getattr_nanos": 49888676, + "fuse_op_lookup_count": 11690, + "fuse_op_lookup_nanos": 121710720, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 97296284, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26582634, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 819870, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1105691655, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 26568776, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16158, + "fuse_read_lane_wait_nanos": 1426341, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32013, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673123, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1543871, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19340, + "lookup_base_count": 23, + "lookup_count": 40292, + "lookup_delta_count": 7193, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6769, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14260, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32908, + "path_resolution_count": 7120, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 783392299, + "agentfs_batcher_commit_txns": 4777, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4688, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9400, + "attr_cache_hits": 26156, + "attr_cache_misses": 30822, + "base_fast_inode_invalidations": 15082, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 981, + "connection_create_count": 9, + "connection_reuse_count": 31814, + "connection_wait_count": 31823, + "connection_wait_nanos": 9259292, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4694, + "fuse_adapter_attr_misses": 5622, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5286, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27135, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 44, + "fuse_dispatch_wait_count": 44, + "fuse_dispatch_wait_nanos": 342116, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10316, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11740, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 556283733, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 41, + "fuse_op_forget_nanos": 580404, + "fuse_op_getattr_count": 10316, + "fuse_op_getattr_nanos": 64531862, + "fuse_op_lookup_count": 11740, + "fuse_op_lookup_nanos": 122436012, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 100368792, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 27524406, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 929330, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1110792824, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 26757039, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16910, + "fuse_read_lane_wait_nanos": 1512866, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37397, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53264516, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1572829, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20742, + "lookup_base_count": 23, + "lookup_count": 40456, + "lookup_delta_count": 7253, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6803, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14346, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33032, + "path_resolution_count": 7165, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 788452315, + "agentfs_batcher_commit_txns": 4781, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4690, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26176, + "attr_cache_misses": 30858, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32831, + "connection_wait_count": 32840, + "connection_wait_nanos": 9560632, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6121, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30265, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11751, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12032, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 11751, + "fuse_op_getattr_nanos": 65862666, + "fuse_op_lookup_count": 12032, + "fuse_op_lookup_nanos": 126977898, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 103600685, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 30037790, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 47489914, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1120354419, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 29014201, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17907, + "fuse_read_lane_wait_nanos": 1604027, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43297, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54445974, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1577122, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20770, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6808, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14924, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 788452315, + "agentfs_batcher_commit_txns": 4781, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4690, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9404, + "attr_cache_hits": 26177, + "attr_cache_misses": 30858, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32839, + "connection_wait_count": 32848, + "connection_wait_nanos": 9564961, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6190, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30341, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11820, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12033, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 11820, + "fuse_op_getattr_nanos": 65918230, + "fuse_op_lookup_count": 12033, + "fuse_op_lookup_nanos": 127002362, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 103600685, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30707958, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 47489914, + "fuse_op_setattr_count": 4769, + "fuse_op_setattr_nanos": 1120354419, + "fuse_op_write_count": 4841, + "fuse_op_write_nanos": 29014201, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17909, + "fuse_read_lane_wait_nanos": 1604537, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43373, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54445974, + "fuse_write_count": 4841, + "fuse_write_lane_wait_count": 10260, + "fuse_write_lane_wait_nanos": 1577122, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20770, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6808, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 525626 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26177, + "attr_cache_misses": 30882, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32871, + "connection_wait_count": 32880, + "connection_wait_nanos": 9590305, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6206, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5287, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30365, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 11836, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12033, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 11836, + "fuse_op_getattr_nanos": 65953076, + "fuse_op_lookup_count": 12033, + "fuse_op_lookup_nanos": 127002362, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 105425428, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30707958, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 47489914, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17917, + "fuse_read_lane_wait_nanos": 1607025, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43413, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1580054, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20786, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6808, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2103531 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26177, + "attr_cache_misses": 30882, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32871, + "connection_wait_count": 32880, + "connection_wait_nanos": 9590305, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 8214, + "fuse_adapter_attr_misses": 5630, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5380, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 32466, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13844, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12126, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13844, + "fuse_op_getattr_nanos": 67423333, + "fuse_op_lookup_count": 12126, + "fuse_op_lookup_nanos": 127124191, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 105434258, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 30707958, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 47489914, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17917, + "fuse_read_lane_wait_nanos": 1607025, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45526, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1580054, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20786, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6901, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2103531 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32886, + "connection_wait_count": 32895, + "connection_wait_nanos": 9606949, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 1580054, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2103531 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32889, + "connection_wait_count": 32898, + "connection_wait_nanos": 9613574, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1581400, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2249063 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "agentfs" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32889, + "connection_wait_count": 32898, + "connection_wait_nanos": 9613574, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1581400, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2249063 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "fuse_session" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 790560838, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4698, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9412, + "attr_cache_hits": 26180, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15122, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1001, + "connection_create_count": 9, + "connection_reuse_count": 32889, + "connection_wait_count": 32898, + "connection_wait_nanos": 9613574, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 8258, + "fuse_adapter_attr_misses": 5631, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5623, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 32778, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 401757, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13889, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12373, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 557340473, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 686, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 626255, + "fuse_op_getattr_count": 13889, + "fuse_op_getattr_nanos": 67519092, + "fuse_op_lookup_count": 12373, + "fuse_op_lookup_nanos": 127472197, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1024, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 105452198, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 31056244, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 48252398, + "fuse_op_setattr_count": 4777, + "fuse_op_setattr_nanos": 1124507489, + "fuse_op_write_count": 4849, + "fuse_op_write_nanos": 29938467, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17932, + "fuse_read_lane_wait_nanos": 1611323, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45874, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452372, + "fuse_write_count": 4849, + "fuse_write_lane_wait_count": 10270, + "fuse_write_lane_wait_nanos": 1581400, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20788, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7144, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2249063 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "run_parent" + } + ], + "returncode": 0, + "stderr_bytes": 40560, + "stderr_tail": "atcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26177,\"attr_cache_misses\":30882,\"base_fast_inode_invalidations\":15122,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":267,\"chunk_read_queries\":122,\"chunk_write_chunks\":1001,\"connection_create_count\":9,\"connection_reuse_count\":32871,\"connection_wait_count\":32880,\"connection_wait_nanos\":9590305,\"dentry_cache_hits\":26126,\"dentry_cache_misses\":14052,\"fuse_adapter_attr_hits\":8214,\"fuse_adapter_attr_misses\":5630,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6734,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5380,\"fuse_adapter_negative_misses\":6734,\"fuse_callback_count\":32466,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":401757,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13844,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12126,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":557340473,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":686,\"fuse_op_forget_count\":44,\"fuse_op_forget_nanos\":626255,\"fuse_op_getattr_count\":13844,\"fuse_op_getattr_nanos\":67423333,\"fuse_op_lookup_count\":12126,\"fuse_op_lookup_nanos\":127124191,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1024,\"fuse_op_other_count\":3581,\"fuse_op_other_nanos\":105434258,\"fuse_op_read_count\":239,\"fuse_op_read_nanos\":30707958,\"fuse_op_readdirplus_count\":1407,\"fuse_op_readdirplus_nanos\":47489914,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1124507489,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":29938467,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":239,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17917,\"fuse_read_lane_wait_nanos\":1607025,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1407,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45526,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452372,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":1580054,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20786,\"lookup_base_count\":23,\"lookup_count\":41056,\"lookup_delta_count\":7549,\"lookup_whiteout_count\":0,\"negative_cache_hits\":6901,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46085,\"negative_lookup_count\":14924,\"path_cache_hits\":26126,\"path_cache_misses\":14052,\"path_component_count\":37638,\"path_resolution_count\":8142,\"readdir_count\":1,\"readdir_plus_count\":701,\"wal_checkpoint_count\":11,\"wal_checkpoint_nanos\":2103531},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"phase-checkpoint-6\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":790560838,\"agentfs_batcher_commit_txns\":4789,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4698,\"agentfs_batcher_drains_timer\":15,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26180,\"attr_cache_misses\":30884,\"base_fast_inode_invalidations\":15122,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1001,\"connection_create_count\":9,\"connection_reuse_count\":32886,\"connection_wait_count\":32895,\"connection_wait_nanos\":9606949,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":8258,\"fuse_adapter_attr_misses\":5631,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5623,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32778,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":401757,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13889,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12373,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":557340473,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":686,\"fuse_op_forget_count\":44,\"fuse_op_forget_nanos\":626255,\"fuse_op_getattr_count\":13889,\"fuse_op_getattr_nanos\":67519092,\"fuse_op_lookup_count\":12373,\"fuse_op_lookup_nanos\":127472197,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1024,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":105452198,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":31056244,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":48252398,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1124507489,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":29938467,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17932,\"fuse_read_lane_wait_nanos\":1611323,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45874,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452372,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":1580054,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20788,\"lookup_base_count\":23,\"lookup_count\":41064,\"lookup_delta_count\":7553,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7144,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14926,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37680,\"path_resolution_count\":8152,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":11,\"wal_checkpoint_nanos\":2103531},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"phase-checkpoint-7\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":790560838,\"agentfs_batcher_commit_txns\":4789,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4698,\"agentfs_batcher_drains_timer\":15,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26180,\"attr_cache_misses\":30884,\"base_fast_inode_invalidations\":15122,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1001,\"connection_create_count\":9,\"connection_reuse_count\":32889,\"connection_wait_count\":32898,\"connection_wait_nanos\":9613574,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":8258,\"fuse_adapter_attr_misses\":5631,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5623,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32778,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":401757,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13889,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12373,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":557340473,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":686,\"fuse_op_forget_count\":44,\"fuse_op_forget_nanos\":626255,\"fuse_op_getattr_count\":13889,\"fuse_op_getattr_nanos\":67519092,\"fuse_op_lookup_count\":12373,\"fuse_op_lookup_nanos\":127472197,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1024,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":105452198,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":31056244,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":48252398,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1124507489,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":29938467,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17932,\"fuse_read_lane_wait_nanos\":1611323,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45874,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452372,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10270,\"fuse_write_lane_wait_nanos\":1581400,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20788,\"lookup_base_count\":23,\"lookup_count\":41064,\"lookup_delta_count\":7553,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7144,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14926,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37680,\"path_resolution_count\":8152,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":2249063},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"agentfs\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":790560838,\"agentfs_batcher_commit_txns\":4789,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4698,\"agentfs_batcher_drains_timer\":15,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26180,\"attr_cache_misses\":30884,\"base_fast_inode_invalidations\":15122,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1001,\"connection_create_count\":9,\"connection_reuse_count\":32889,\"connection_wait_count\":32898,\"connection_wait_nanos\":9613574,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":8258,\"fuse_adapter_attr_misses\":5631,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5623,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32778,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":401757,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13889,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12373,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":557340473,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":686,\"fuse_op_forget_count\":44,\"fuse_op_forget_nanos\":626255,\"fuse_op_getattr_count\":13889,\"fuse_op_getattr_nanos\":67519092,\"fuse_op_lookup_count\":12373,\"fuse_op_lookup_nanos\":127472197,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1024,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":105452198,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":31056244,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":48252398,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1124507489,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":29938467,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17932,\"fuse_read_lane_wait_nanos\":1611323,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45874,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452372,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10270,\"fuse_write_lane_wait_nanos\":1581400,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20788,\"lookup_base_count\":23,\"lookup_count\":41064,\"lookup_delta_count\":7553,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7144,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14926,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37680,\"path_resolution_count\":8152,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":2249063},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"fuse_session\"}\n\nSession: git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33\n\nTo resume this session:\n agentfs run --session git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33\n\nTo see what changed:\n agentfs diff git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":790560838,\"agentfs_batcher_commit_txns\":4789,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4698,\"agentfs_batcher_drains_timer\":15,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9412,\"attr_cache_hits\":26180,\"attr_cache_misses\":30884,\"base_fast_inode_invalidations\":15122,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1001,\"connection_create_count\":9,\"connection_reuse_count\":32889,\"connection_wait_count\":32898,\"connection_wait_nanos\":9613574,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":8258,\"fuse_adapter_attr_misses\":5631,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5623,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":32778,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":47,\"fuse_dispatch_wait_count\":47,\"fuse_dispatch_wait_nanos\":401757,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13889,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12373,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":557340473,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":686,\"fuse_op_forget_count\":44,\"fuse_op_forget_nanos\":626255,\"fuse_op_getattr_count\":13889,\"fuse_op_getattr_nanos\":67519092,\"fuse_op_lookup_count\":12373,\"fuse_op_lookup_nanos\":127472197,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1024,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":105452198,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":31056244,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":48252398,\"fuse_op_setattr_count\":4777,\"fuse_op_setattr_nanos\":1124507489,\"fuse_op_write_count\":4849,\"fuse_op_write_nanos\":29938467,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17932,\"fuse_read_lane_wait_nanos\":1611323,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45874,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452372,\"fuse_write_count\":4849,\"fuse_write_lane_wait_count\":10270,\"fuse_write_lane_wait_nanos\":1581400,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20788,\"lookup_base_count\":23,\"lookup_count\":41064,\"lookup_delta_count\":7553,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7144,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14926,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37680,\"path_resolution_count\":8152,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":2249063},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"run_parent\"}\n", + "stdout_bytes": 18746, + "stdout_tail": "2026-07-03T11:39:15.539803Z INFO agentfs::fuser::session: resolved FUSE dispatch mode: parallel workers=7 queue_capacity=28\n2026-07-03T11:39:15.540002Z INFO agentfs::fuse: advertising FUSE_OVER_IO_URING\n2026-07-03T11:39:15.540037Z INFO agentfs::fuser::uring: starting fuse-over-io_uring queues nr_queues=14 depth=4\n2026-07-03T11:39:16.883531Z WARN agentfs::fuser::request: Request RequestId(28571): Failed to send reply: No such file or directory (os error 2)\n2026-07-03T11:39:17.883528Z WARN agentfs::fuser::request: Request RequestId(51593): Failed to send reply: No such file or directory (os error 2)\n{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.07983500800037291, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work\", \"duration_seconds\": 0.02311817200097721, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work\", \"duration_seconds\": 0.03407841798616573, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work\", \"duration_seconds\": 0.022595963993808255, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.008319867003592663, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work\", \"duration_seconds\": 0.16707537201000378, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work\", \"duration_seconds\": 0.04896818099950906, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/mirror.git\", \"/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/agentfs-base\", \"duration_seconds\": 2.7956893559894525, \"returncode\": 0, \"stderr_bytes\": 2175, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\nUpdating files: 42% (1969/4644)\\nUpdating files: 43% (1997/4644)\\nUpdating files: 44% (2044/4644)\\nUpdating files: 45% (2090/4644)\\nUpdating files: 46% (2137/4644)\\nUpdating files: 47% (2183/4644)\\nUpdating files: 48% (2230/4644)\\nUpdating files: 49% (2276/4644)\\nUpdating files: 50% (2322/4644)\\nUpdating files: 51% (2369/4644)\\nUpdating files: 52% (2415/4644)\\nUpdating files: 53% (2462/4644)\\nUpdating files: 54% (2508/4644)\\nUpdating files: 55% (2555/4644)\\nUpdating files: 56% (2601/4644)\\nUpdating files: 57% (2648/4644)\\nUpdating files: 58% (2694/4644)\\nUpdating files: 59% (2740/4644)\\nUpdating files: 60% (2787/4644)\\nUpdating files: 61% (2833/4644)\\nUpdating files: 62% (2880/4644)\\nUpdating files: 63% (2926/4644)\\nUpdating files: 64% (2973/4644)\\nUpdating files: 65% (3019/4644)\\nUpdating files: 66% (3066/4644)\\nUpdating files: 67% (3112/4644)\\nUpdating files: 68% (3158/4644)\\nUpdating files: 69% (3205/4644)\\nUpdating files: 70% (3251/4644)\\nUpdating files: 71% (3298/4644)\\nUpdating files: 72% (3344/4644)\\nUpdating files: 73% (3391/4644)\\nUpdating files: 74% (3437/4644)\\nUpdating files: 75% (3483/4644)\\nUpdating files: 76% (3530/4644)\\nUpdating files: 77% (3576/4644)\\nUpdating files: 78% (3623/4644)\\nUpdating files: 79% (3669/4644)\\nUpdating files: 79% (3715/4644)\\nUpdating files: 80% (3716/4644)\\nUpdating files: 81% (3762/4644)\\nUpdating files: 82% (3809/4644)\\nUpdating files: 83% (3855/4644)\\nUpdating files: 84% (3901/4644)\\nUpdating files: 85% (3948/4644)\\nUpdating files: 86% (3994/4644)\\nUpdating files: 87% (4041/4644)\\nUpdating files: 88% (4087/4644)\\nUpdating files: 89% (4134/4644)\\nUpdating files: 90% (4180/4644)\\nUpdating files: 91% (4227/4644)\\nUpdating files: 92% (4273/4644)\\nUpdating files: 93% (4319/4644)\\nUpdating files: 94% (4366/4644)\\nUpdating files: 95% (4412/4644)\\nUpdating files: 96% (4459/4644)\\nUpdating files: 97% (4505/4644)\\nUpdating files: 98% (4552/4644)\\nUpdating files: 99% (4598/4644)\\nUpdating files: 100% (4644/4644)\\nUpdating files: 100% (4644/4644), done.\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work\", \"duration_seconds\": 0.05520079100097064, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work\", \"duration_seconds\": 0.11257522000232711, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.05152055200596806, \"clone\": 2.795723178991466, \"diff\": 0.07983500800037291, \"edit\": 0.008319867003592663, \"fsck\": 0.16708888999710325, \"read_search\": 0.005946407007286325, \"status\": 0.1678003530105343}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work\", \"duration_seconds\": 0.004040564992465079, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 3.9769298949977383}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.07983500800037291, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work", + "duration_seconds": 0.02311817200097721, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work", + "duration_seconds": 0.03407841798616573, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work", + "duration_seconds": 0.022595963993808255, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.008319867003592663, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work", + "duration_seconds": 0.16707537201000378, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work", + "duration_seconds": 0.04896818099950906, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/mirror.git", + "/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/agentfs-base", + "duration_seconds": 2.7956893559894525, + "returncode": 0, + "stderr_bytes": 2175, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\nUpdating files: 42% (1969/4644)\nUpdating files: 43% (1997/4644)\nUpdating files: 44% (2044/4644)\nUpdating files: 45% (2090/4644)\nUpdating files: 46% (2137/4644)\nUpdating files: 47% (2183/4644)\nUpdating files: 48% (2230/4644)\nUpdating files: 49% (2276/4644)\nUpdating files: 50% (2322/4644)\nUpdating files: 51% (2369/4644)\nUpdating files: 52% (2415/4644)\nUpdating files: 53% (2462/4644)\nUpdating files: 54% (2508/4644)\nUpdating files: 55% (2555/4644)\nUpdating files: 56% (2601/4644)\nUpdating files: 57% (2648/4644)\nUpdating files: 58% (2694/4644)\nUpdating files: 59% (2740/4644)\nUpdating files: 60% (2787/4644)\nUpdating files: 61% (2833/4644)\nUpdating files: 62% (2880/4644)\nUpdating files: 63% (2926/4644)\nUpdating files: 64% (2973/4644)\nUpdating files: 65% (3019/4644)\nUpdating files: 66% (3066/4644)\nUpdating files: 67% (3112/4644)\nUpdating files: 68% (3158/4644)\nUpdating files: 69% (3205/4644)\nUpdating files: 70% (3251/4644)\nUpdating files: 71% (3298/4644)\nUpdating files: 72% (3344/4644)\nUpdating files: 73% (3391/4644)\nUpdating files: 74% (3437/4644)\nUpdating files: 75% (3483/4644)\nUpdating files: 76% (3530/4644)\nUpdating files: 77% (3576/4644)\nUpdating files: 78% (3623/4644)\nUpdating files: 79% (3669/4644)\nUpdating files: 79% (3715/4644)\nUpdating files: 80% (3716/4644)\nUpdating files: 81% (3762/4644)\nUpdating files: 82% (3809/4644)\nUpdating files: 83% (3855/4644)\nUpdating files: 84% (3901/4644)\nUpdating files: 85% (3948/4644)\nUpdating files: 86% (3994/4644)\nUpdating files: 87% (4041/4644)\nUpdating files: 88% (4087/4644)\nUpdating files: 89% (4134/4644)\nUpdating files: 90% (4180/4644)\nUpdating files: 91% (4227/4644)\nUpdating files: 92% (4273/4644)\nUpdating files: 93% (4319/4644)\nUpdating files: 94% (4366/4644)\nUpdating files: 95% (4412/4644)\nUpdating files: 96% (4459/4644)\nUpdating files: 97% (4505/4644)\nUpdating files: 98% (4552/4644)\nUpdating files: 99% (4598/4644)\nUpdating files: 100% (4644/4644)\nUpdating files: 100% (4644/4644), done.\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work", + "duration_seconds": 0.05520079100097064, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work", + "duration_seconds": 0.11257522000232711, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.05152055200596806, + "clone": 2.795723178991466, + "diff": 0.07983500800037291, + "edit": 0.008319867003592663, + "fsck": 0.16708888999710325, + "read_search": 0.005946407007286325, + "status": 0.1678003530105343 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/agentfs-base/work", + "duration_seconds": 0.004040564992465079, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 3.9769298949977383 + } + }, + "base_tree": { + "after": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "4d03469ad16c4d46174f06f298f214b902f622319125cd3270835dd500de4ddf", + "symlinks": 0 + }, + "before": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "4d03469ad16c4d46174f06f298f214b902f622319125cd3270835dd500de4ddf", + "symlinks": 0 + }, + "unchanged": true + }, + "benchmark": "phase7-git-workload", + "command": { + "agentfs_prefix": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33", + "--no-default-allows", + "--" + ], + "argv": [ + "/home/ain3sh/factory/vfs/scripts/validation/git-workload-benchmark.py", + "--output", + "/home/ain3sh/factory/vfs/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-02.json", + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "workload_argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ] + }, + "correctness": { + "agentfs_backup_verify": true, + "agentfs_base_unchanged": true, + "agentfs_db_inspectable": true, + "agentfs_integrity_require_portable": true, + "agentfs_no_nonempty_sidecars": true, + "agentfs_portable": true, + "agentfs_returncode_zero": true, + "equivalence": { + "agentfs": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + }, + "checked": true, + "equivalent": true, + "native": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + } + }, + "native_returncode_zero": true, + "passed": true, + "performance_passed": false + }, + "database": { + "after": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-j7ifkggm/home/.agentfs/run/git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33/delta.db" + } + ], + "path": "/tmp/agentfs-git-workload-j7ifkggm/home/.agentfs/run/git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33/delta.db", + "total_bytes": 57147392 + }, + "backup": { + "artifacts": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-j7ifkggm/git-workload-backup.db" + } + ], + "path": "/tmp/agentfs-git-workload-j7ifkggm/git-workload-backup.db", + "total_bytes": 57147392 + }, + "inspect": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "path": "/tmp/agentfs-git-workload-j7ifkggm/git-workload-backup.db", + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "backup", + "/tmp/agentfs-git-workload-j7ifkggm/home/.agentfs/run/git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33/delta.db", + "/tmp/agentfs-git-workload-j7ifkggm/git-workload-backup.db", + "--verify" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm", + "duration_seconds": 2.6917907019960694, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 241, + "stdout_tail": "Source: /tmp/agentfs-git-workload-j7ifkggm/home/.agentfs/run/git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33/delta.db\nBackup: /tmp/agentfs-git-workload-j7ifkggm/git-workload-backup.db\nCheckpoint: complete\nCopy: complete\nVerification: complete\n", + "timed_out": false + } + }, + "inspect_after": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "integrity": { + "result": { + "checks": [ + { + "detail": "ok", + "name": "pragma.integrity_check", + "ok": true, + "violating_rows": null + }, + { + "detail": "present", + "name": "schema.table.fs_config", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_symlink", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.kv_store", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.tool_calls", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 0.5", + "name": "config.schema_version", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 65536", + "name": "config.chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 16384", + "name": "config.inline_threshold", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.kind_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_has_no_chunks", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunked_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_size_matches_blob", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.non_regular_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_reference_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunk_length_within_chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 1, expected 1", + "name": "namespace.root_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_is_directory", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_target_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_root_inode_has_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_names_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_directory_nlink_matches_dentries", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.directory_nlink_positive", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.rows_reference_symlink_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.inodes_have_rows", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable: no partial-origin rows", + "name": "overlay.portability_status", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable requirement satisfied", + "name": "overlay.require_portable", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_regular", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_sizes_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_paths_absolute", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_references_partial_origin", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_unique", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_index_in_range", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.whiteout_paths_absolute", + "ok": true, + "violating_rows": 0 + } + ], + "database": "/tmp/agentfs-git-workload-j7ifkggm/home/.agentfs/run/git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33/delta.db", + "ok": true, + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true + }, + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "integrity", + "/tmp/agentfs-git-workload-j7ifkggm/home/.agentfs/run/git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33/delta.db", + "--json", + "--require-portable" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm", + "duration_seconds": 2.6074196609988576, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 6395, + "stdout_tail": "{\n \"database\": \"/tmp/agentfs-git-workload-j7ifkggm/home/.agentfs/run/git-workload-2bd2eb5e81e742bda053e0f8c0e6ad33/delta.db\",\n \"ok\": true,\n \"portable\": true,\n \"origin_backed\": false,\n \"partial_origin_rows\": 0,\n \"checks\": [\n {\n \"name\": \"pragma.integrity_check\",\n \"ok\": true,\n \"detail\": \"ok\",\n \"violating_rows\": null\n },\n {\n \"name\": \"schema.table.fs_config\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_inode\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_dentry\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_data\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_symlink\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.kv_store\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.tool_calls\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.schema_version\",\n \"ok\": true,\n \"detail\": \"found 0.5\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.chunk_size\",\n \"ok\": true,\n \"detail\": \"found 65536\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.inline_threshold\",\n \"ok\": true,\n \"detail\": \"found 16384\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.kind_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_has_no_chunks\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunked_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_size_matches_blob\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.non_regular_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_reference_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunk_length_within_chunk_size\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.root_inode\",\n \"ok\": true,\n \"detail\": \"found 1, expected 1\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_is_directory\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_target_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_root_inode_has_dentry\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_names_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_directory_nlink_matches_dentries\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.directory_nlink_positive\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.rows_reference_symlink_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.inodes_have_rows\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.portability_status\",\n \"ok\": true,\n \"detail\": \"portable: no partial-origin rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.require_portable\",\n \"ok\": true,\n \"detail\": \"portable requirement satisfied\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_regular\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_sizes_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_references_partial_origin\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_unique\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_index_in_range\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.whiteout_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n }\n ]\n}\n", + "timed_out": false + } + }, + "nonempty_sidecars": false + }, + "environment": { + "AGENTFS_BIN": "/home/ain3sh/factory/vfs/target/release/agentfs", + "AGENTFS_PROFILE": "1" + }, + "git_commit": "ae1e13fcb1c52d5c57be051610a04aa9a318f2a5", + "kept_temp": false, + "native": { + "run": { + "argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/native", + "duration_seconds": 1.3324775060027605, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 15985, + "stdout_tail": "{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.2956617010058835, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/native/work\", \"duration_seconds\": 0.10290846700081602, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/native/work\", \"duration_seconds\": 0.09795400100119878, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/native/work\", \"duration_seconds\": 0.09476038000138942, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.0005059730028733611, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/native/work\", \"duration_seconds\": 0.23166249600762967, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/native/work\", \"duration_seconds\": 0.17857912500039674, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-j7ifkggm/native/mirror.git\", \"/tmp/agentfs-git-workload-j7ifkggm/native/work\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/native\", \"duration_seconds\": 0.3216873180062976, \"returncode\": 0, \"stderr_bytes\": 149, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-j7ifkggm/native/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/native/work\", \"duration_seconds\": 0.12663498798792716, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/native/work\", \"duration_seconds\": 0.11151765799149871, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.18079679299262352, \"clone\": 0.32173172100738157, \"diff\": 0.2956617010058835, \"edit\": 0.0005059730028733611, \"fsck\": 0.23167631099931896, \"read_search\": 0.006168587991851382, \"status\": 0.23817145101202186}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-j7ifkggm/native/work\", \"duration_seconds\": 0.0037229040026431903, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 1.2749006189987995}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.2956617010058835, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/native/work", + "duration_seconds": 0.10290846700081602, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/native/work", + "duration_seconds": 0.09795400100119878, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/native/work", + "duration_seconds": 0.09476038000138942, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.0005059730028733611, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/native/work", + "duration_seconds": 0.23166249600762967, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/native/work", + "duration_seconds": 0.17857912500039674, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-j7ifkggm/native/mirror.git", + "/tmp/agentfs-git-workload-j7ifkggm/native/work" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/native", + "duration_seconds": 0.3216873180062976, + "returncode": 0, + "stderr_bytes": 149, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-j7ifkggm/native/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/native/work", + "duration_seconds": 0.12663498798792716, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/native/work", + "duration_seconds": 0.11151765799149871, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.18079679299262352, + "clone": 0.32173172100738157, + "diff": 0.2956617010058835, + "edit": 0.0005059730028733611, + "fsck": 0.23167631099931896, + "read_search": 0.006168587991851382, + "status": 0.23817145101202186 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-j7ifkggm/native/work", + "duration_seconds": 0.0037229040026431903, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 1.2749006189987995 + } + }, + "parameters": { + "edit_files": 8, + "fixture_dirs": 8, + "fixture_file_size_bytes": 1024, + "fixture_files": 96, + "read_bytes": 4096, + "read_files": 64, + "search_token": "AGENTFS_TOKEN", + "skip_fsck": false, + "timeout_seconds": 180.0 + }, + "schema_version": 1, + "source": { + "kind": "source", + "mirror_head": "7d47056ea42636271ac020b86347fbbef49490aa", + "path": "/home/ain3sh/factory/vfs/.agents/benchmarks/fixtures/codex" + }, + "summary": { + "agentfs_base_unchanged": true, + "agentfs_seconds": 3.9769298949977383, + "all_equivalent": true, + "correctness_passed": true, + "native_seconds": 1.2749006189987995, + "passed": true, + "performance_passed": false, + "phase_ratios": { + "checkout": { + "agentfs_seconds": 0.05152055200596806, + "native_seconds": 0.18079679299262352, + "ratio": 0.28496385999540425 + }, + "clone": { + "agentfs_seconds": 2.795723178991466, + "native_seconds": 0.32173172100738157, + "ratio": 8.689609996296644 + }, + "diff": { + "agentfs_seconds": 0.07983500800037291, + "native_seconds": 0.2956617010058835, + "ratio": 0.2700214729495324 + }, + "edit": { + "agentfs_seconds": 0.008319867003592663, + "native_seconds": 0.0005059730028733611, + "ratio": 16.443302224318526 + }, + "fsck": { + "agentfs_seconds": 0.16708888999710325, + "native_seconds": 0.23167631099931896, + "ratio": 0.72121698276521 + }, + "read_search": { + "agentfs_seconds": 0.005946407007286325, + "native_seconds": 0.006168587991851382, + "ratio": 0.9639818731841785 + }, + "status": { + "agentfs_seconds": 0.1678003530105343, + "native_seconds": 0.23817145101202186, + "ratio": 0.7045359647326684 + } + }, + "ratio": 3.11940384664719, + "threshold_failures": [ + { + "agentfs_seconds": 2.795723178991466, + "native_seconds": 0.32173172100738157, + "phase": "clone", + "ratio": 8.689609996296644 + }, + { + "agentfs_seconds": 0.008319867003592663, + "native_seconds": 0.0005059730028733611, + "phase": "edit", + "ratio": 16.443302224318526 + } + ] + }, + "temp_dir": "/tmp/agentfs-git-workload-j7ifkggm" +} diff --git a/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-03.json b/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-03.json new file mode 100644 index 00000000..cc1a64d6 --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-03.json @@ -0,0 +1,6636 @@ +{ + "agentfs": { + "bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "db_path": "/tmp/agentfs-git-workload-pl9ksnf0/home/.agentfs/run/git-workload-4d8592c65a644b99a57302f36fd3ddff/delta.db", + "per_phase_counters": { + "checkpoint_count": 7, + "label_count": 7, + "labels_aligned": true, + "phases": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 829850190, + "agentfs_batcher_commit_txns": 4776, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4680, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9384, + "attr_cache_hits": 25922, + "attr_cache_misses": 29050, + "base_fast_inode_invalidations": 15027, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create_count": 2, + "connection_reuse_count": 31194, + "connection_wait_count": 31196, + "connection_wait_nanos": 10452744, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 92, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5283, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21782, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 418205, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52456532, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5026, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11700, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 577281862, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 761283, + "fuse_op_getattr_count": 5026, + "fuse_op_getattr_nanos": 53165859, + "fuse_op_lookup_count": 11700, + "fuse_op_lookup_nanos": 135481527, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 116756992, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26555120, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 939247, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1181414602, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 29006612, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16154, + "fuse_read_lane_wait_nanos": 1521801, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32005, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1784205, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40292, + "lookup_delta_count": 7193, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6779, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14260, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32908, + "path_resolution_count": 7120, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "phase": "clone", + "seq": 1 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 2, + "agentfs_batcher_commit_latency_ns_total": 3835877, + "agentfs_batcher_commit_txns": 9, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 5, + "agentfs_batcher_drains_timer": 3, + "agentfs_batcher_enqueues": 10, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 15, + "attr_cache_hits": 268, + "attr_cache_misses": 1804, + "base_fast_inode_invalidations": 51, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 15, + "connection_create_count": 5, + "connection_reuse_count": 625, + "connection_wait_count": 630, + "connection_wait_nanos": 259160, + "dentry_cache_hits": 73, + "dentry_cache_misses": 56, + "fuse_adapter_attr_hits": 4578, + "fuse_adapter_attr_misses": 723, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 35, + "fuse_adapter_inval_entry_notifications": 10, + "fuse_adapter_inval_inode_notifications": 28, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 12, + "fuse_adapter_negative_misses": 35, + "fuse_callback_count": 5371, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 6, + "fuse_dispatch_parallel_tasks": 9, + "fuse_dispatch_wait_count": 9, + "fuse_dispatch_wait_nanos": 52345, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 777744, + "fuse_flush_count": 10, + "fuse_flush_ranges": 10, + "fuse_getattr_count": 5301, + "fuse_ino_file_resolutions": 3, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 49, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 9, + "fuse_op_create_nanos": 1220153, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 9, + "fuse_op_forget_nanos": 61517, + "fuse_op_getattr_count": 5301, + "fuse_op_getattr_nanos": 16281367, + "fuse_op_lookup_count": 49, + "fuse_op_lookup_nanos": 513848, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 15, + "fuse_op_other_nanos": 2845520, + "fuse_op_read_count": 8, + "fuse_op_read_nanos": 970170, + "fuse_op_readdirplus_count": 4, + "fuse_op_readdirplus_nanos": 259795, + "fuse_op_setattr_count": 14, + "fuse_op_setattr_nanos": 4041695, + "fuse_op_write_count": 9, + "fuse_op_write_nanos": 202318, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 8, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 788, + "fuse_read_lane_wait_nanos": 94398, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 4, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5409, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 591393, + "fuse_write_count": 9, + "fuse_write_lane_wait_count": 42, + "fuse_write_lane_wait_nanos": 38263, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 1474, + "lookup_base_count": 0, + "lookup_count": 164, + "lookup_delta_count": 60, + "lookup_whiteout_count": 0, + "negative_cache_hits": 33, + "negative_cache_invalidations": 26, + "negative_cache_misses": 148, + "negative_lookup_count": 86, + "path_cache_hits": 73, + "path_cache_misses": 56, + "path_component_count": 124, + "path_resolution_count": 45, + "readdir_count": 0, + "readdir_plus_count": 2, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "checkout", + "seq": 2 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 4, + "agentfs_batcher_commit_latency_ns_total": 6403812, + "agentfs_batcher_commit_txns": 4, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 2, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 6, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 4, + "attr_cache_hits": 21, + "attr_cache_misses": 40, + "base_fast_inode_invalidations": 24, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 20, + "chunk_read_queries": 12, + "chunk_write_chunks": 20, + "connection_create_count": 0, + "connection_reuse_count": 1018, + "connection_wait_count": 1018, + "connection_wait_nanos": 291234, + "dentry_cache_hits": 20, + "dentry_cache_misses": 570, + "fuse_adapter_attr_hits": 1661, + "fuse_adapter_attr_misses": 10, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 289, + "fuse_adapter_inval_entry_notifications": 4, + "fuse_adapter_inval_inode_notifications": 8, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 2, + "fuse_adapter_negative_misses": 289, + "fuse_callback_count": 3367, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 4, + "fuse_dispatch_wait_count": 4, + "fuse_dispatch_wait_nanos": 81823, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 1181458, + "fuse_flush_count": 6, + "fuse_flush_ranges": 6, + "fuse_getattr_count": 1671, + "fuse_ino_file_resolutions": 2, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 293, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 2, + "fuse_op_create_nanos": 1097557, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 4, + "fuse_op_forget_nanos": 54960, + "fuse_op_getattr_count": 1671, + "fuse_op_getattr_nanos": 1815552, + "fuse_op_lookup_count": 293, + "fuse_op_lookup_nanos": 3799428, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 2762, + "fuse_op_other_nanos": 3327164, + "fuse_op_read_count": 12, + "fuse_op_read_nanos": 2039422, + "fuse_op_readdirplus_count": 1381, + "fuse_op_readdirplus_nanos": 42959334, + "fuse_op_setattr_count": 6, + "fuse_op_setattr_nanos": 11845718, + "fuse_op_write_count": 10, + "fuse_op_write_nanos": 1934292, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 12, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 999, + "fuse_read_lane_wait_nanos": 96000, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1381, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 6137, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 1181458, + "fuse_write_count": 10, + "fuse_write_lane_wait_count": 14, + "fuse_write_lane_wait_nanos": 29604, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 32, + "lookup_base_count": 0, + "lookup_count": 598, + "lookup_delta_count": 295, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6, + "negative_cache_invalidations": 4, + "negative_cache_misses": 873, + "negative_lookup_count": 578, + "path_cache_hits": 20, + "path_cache_misses": 570, + "path_component_count": 4606, + "path_resolution_count": 977, + "readdir_count": 0, + "readdir_plus_count": 688, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "status", + "seq": 3 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 1, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 8, + "connection_wait_count": 8, + "connection_wait_nanos": 11073, + "dentry_cache_hits": 1, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 69, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 1, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 1, + "fuse_callback_count": 76, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 69, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 1, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 69, + "fuse_op_getattr_nanos": 62509, + "fuse_op_lookup_count": 1, + "fuse_op_lookup_nanos": 43621, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 6, + "fuse_op_read_nanos": 1857998, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 6, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 2, + "fuse_read_lane_wait_nanos": 1070, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 76, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 2, + "lookup_delta_count": 1, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 2, + "negative_lookup_count": 0, + "path_cache_hits": 1, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "read_search", + "seq": 4 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 1139080, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 8, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 8, + "attr_cache_hits": 0, + "attr_cache_misses": 24, + "base_fast_inode_invalidations": 16, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 32, + "connection_wait_count": 32, + "connection_wait_nanos": 13726, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 16, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 24, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 6398, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 16, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 16, + "fuse_op_getattr_nanos": 24324, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 8, + "fuse_op_other_nanos": 985156, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 8, + "fuse_op_setattr_nanos": 2230726, + "fuse_op_write_count": 8, + "fuse_op_write_nanos": 387650, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 8, + "fuse_read_lane_wait_nanos": 1232, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 40, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 6398, + "fuse_write_count": 8, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 1219, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 16, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 8, + "wal_checkpoint_nanos": 851020 + }, + "phase": "edit", + "seq": 5 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 2719, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 105, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 2824, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 2719, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 105, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 2719, + "fuse_op_getattr_nanos": 1572739, + "fuse_op_lookup_count": 105, + "fuse_op_lookup_nanos": 131459, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 12, + "fuse_op_other_nanos": 4574, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 2836, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 105, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "diff", + "seq": 6 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 3, + "attr_cache_misses": 2, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 1, + "chunk_read_queries": 1, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 15, + "connection_wait_count": 15, + "connection_wait_nanos": 15261, + "dentry_cache_hits": 4, + "dentry_cache_misses": 2, + "fuse_adapter_attr_hits": 44, + "fuse_adapter_attr_misses": 1, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 4, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 249, + "fuse_adapter_negative_misses": 4, + "fuse_callback_count": 318, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 45, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 253, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 45, + "fuse_op_getattr_nanos": 73412, + "fuse_op_lookup_count": 253, + "fuse_op_lookup_nanos": 369464, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 36, + "fuse_op_other_nanos": 10048, + "fuse_op_read_count": 2, + "fuse_op_read_nanos": 1878077, + "fuse_op_readdirplus_count": 18, + "fuse_op_readdirplus_nanos": 515509, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 2, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 15, + "fuse_read_lane_wait_nanos": 2776, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 18, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 354, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 2, + "lookup_base_count": 0, + "lookup_count": 8, + "lookup_delta_count": 4, + "lookup_whiteout_count": 0, + "negative_cache_hits": 249, + "negative_cache_invalidations": 0, + "negative_cache_misses": 9, + "negative_lookup_count": 2, + "path_cache_hits": 4, + "path_cache_misses": 2, + "path_component_count": 42, + "path_resolution_count": 10, + "readdir_count": 0, + "readdir_plus_count": 9, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "fsck", + "seq": 7 + } + ] + }, + "profile_counters": { + "last_by_source": { + "agentfs": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32895, + "connection_wait_count": 32902, + "connection_wait_nanos": 11049316, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1854144, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2740864 + }, + "fuse_session": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32895, + "connection_wait_count": 32902, + "connection_wait_nanos": 11049316, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1854144, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2740864 + }, + "phase-checkpoint-1": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 829850190, + "agentfs_batcher_commit_txns": 4776, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4680, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9384, + "attr_cache_hits": 25922, + "attr_cache_misses": 29050, + "base_fast_inode_invalidations": 15027, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create_count": 2, + "connection_reuse_count": 31194, + "connection_wait_count": 31196, + "connection_wait_nanos": 10452744, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 92, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5283, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21782, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 418205, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52456532, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5026, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11700, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 577281862, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 761283, + "fuse_op_getattr_count": 5026, + "fuse_op_getattr_nanos": 53165859, + "fuse_op_lookup_count": 11700, + "fuse_op_lookup_nanos": 135481527, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 116756992, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26555120, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 939247, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1181414602, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 29006612, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16154, + "fuse_read_lane_wait_nanos": 1521801, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32005, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1784205, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40292, + "lookup_delta_count": 7193, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6779, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14260, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32908, + "path_resolution_count": 7120, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "phase-checkpoint-2": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 833686067, + "agentfs_batcher_commit_txns": 4785, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4685, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26190, + "attr_cache_misses": 30854, + "base_fast_inode_invalidations": 15078, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create_count": 7, + "connection_reuse_count": 31819, + "connection_wait_count": 31826, + "connection_wait_nanos": 10711904, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4670, + "fuse_adapter_attr_misses": 5657, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27153, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 470550, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10327, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11749, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 578502015, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 822800, + "fuse_op_getattr_count": 10327, + "fuse_op_getattr_nanos": 69447226, + "fuse_op_lookup_count": 11749, + "fuse_op_lookup_nanos": 135995375, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 119602512, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 27525290, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1199042, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1185456297, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 29208930, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16942, + "fuse_read_lane_wait_nanos": 1616199, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37414, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1822468, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20808, + "lookup_base_count": 23, + "lookup_count": 40456, + "lookup_delta_count": 7253, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6812, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14346, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33032, + "path_resolution_count": 7165, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "phase-checkpoint-3": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 840089879, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26211, + "attr_cache_misses": 30894, + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32837, + "connection_wait_count": 32844, + "connection_wait_nanos": 11003138, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6331, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30520, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11998, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12042, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 11998, + "fuse_op_getattr_nanos": 71262778, + "fuse_op_lookup_count": 12042, + "fuse_op_lookup_nanos": 139794803, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 122929676, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 29564712, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 44158376, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1197302015, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31143222, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17941, + "fuse_read_lane_wait_nanos": 1712199, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43551, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1852072, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20840, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6818, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14924, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "phase-checkpoint-4": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 840089879, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26212, + "attr_cache_misses": 30894, + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32845, + "connection_wait_count": 32852, + "connection_wait_nanos": 11014211, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6400, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30596, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 12067, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 12067, + "fuse_op_getattr_nanos": 71325287, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 139838424, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 122929676, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 31422710, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 44158376, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1197302015, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31143222, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17943, + "fuse_read_lane_wait_nanos": 1713269, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43627, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1852072, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20840, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6818, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "phase-checkpoint-5": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26212, + "attr_cache_misses": 30918, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32877, + "connection_wait_count": 32884, + "connection_wait_nanos": 11027937, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6416, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30620, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12083, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 12083, + "fuse_op_getattr_nanos": 71349611, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 139838424, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 123914832, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 31422710, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 44158376, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17951, + "fuse_read_lane_wait_nanos": 1714501, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43667, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1853291, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20856, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6818, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2582410 + }, + "phase-checkpoint-6": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26212, + "attr_cache_misses": 30918, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32877, + "connection_wait_count": 32884, + "connection_wait_nanos": 11027937, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 9135, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5402, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 33444, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14802, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12148, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14802, + "fuse_op_getattr_nanos": 72922350, + "fuse_op_lookup_count": 12148, + "fuse_op_lookup_nanos": 139969883, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 123919406, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 31422710, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 44158376, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17951, + "fuse_read_lane_wait_nanos": 1714501, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46503, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1853291, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20856, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2582410 + }, + "phase-checkpoint-7": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32892, + "connection_wait_count": 32899, + "connection_wait_nanos": 11043198, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1853291, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2582410 + }, + "run_parent": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32895, + "connection_wait_count": 32902, + "connection_wait_nanos": 11049316, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1854144, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2740864 + } + }, + "max_counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32895, + "connection_wait_count": 32902, + "connection_wait_nanos": 11049316, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1854144, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2740864 + }, + "summary_count": 10 + }, + "profile_enabled": true, + "profile_summary_count": 10, + "session": "git-workload-4d8592c65a644b99a57302f36fd3ddff" + }, + "agentfs_overlay": { + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 829850190, + "agentfs_batcher_commit_txns": 4776, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4680, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9384, + "attr_cache_hits": 25922, + "attr_cache_misses": 29050, + "base_fast_inode_invalidations": 15027, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create_count": 2, + "connection_reuse_count": 31194, + "connection_wait_count": 31196, + "connection_wait_nanos": 10452744, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 92, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5283, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21782, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 418205, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52456532, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5026, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11700, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 577281862, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 761283, + "fuse_op_getattr_count": 5026, + "fuse_op_getattr_nanos": 53165859, + "fuse_op_lookup_count": 11700, + "fuse_op_lookup_nanos": 135481527, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 116756992, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26555120, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 939247, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1181414602, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 29006612, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16154, + "fuse_read_lane_wait_nanos": 1521801, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32005, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1784205, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40292, + "lookup_delta_count": 7193, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6779, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14260, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32908, + "path_resolution_count": 7120, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 833686067, + "agentfs_batcher_commit_txns": 4785, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4685, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26190, + "attr_cache_misses": 30854, + "base_fast_inode_invalidations": 15078, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create_count": 7, + "connection_reuse_count": 31819, + "connection_wait_count": 31826, + "connection_wait_nanos": 10711904, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4670, + "fuse_adapter_attr_misses": 5657, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27153, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 470550, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10327, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11749, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 578502015, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 822800, + "fuse_op_getattr_count": 10327, + "fuse_op_getattr_nanos": 69447226, + "fuse_op_lookup_count": 11749, + "fuse_op_lookup_nanos": 135995375, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 119602512, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 27525290, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1199042, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1185456297, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 29208930, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16942, + "fuse_read_lane_wait_nanos": 1616199, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37414, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1822468, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20808, + "lookup_base_count": 23, + "lookup_count": 40456, + "lookup_delta_count": 7253, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6812, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14346, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33032, + "path_resolution_count": 7165, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 840089879, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26211, + "attr_cache_misses": 30894, + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32837, + "connection_wait_count": 32844, + "connection_wait_nanos": 11003138, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6331, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30520, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11998, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12042, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 11998, + "fuse_op_getattr_nanos": 71262778, + "fuse_op_lookup_count": 12042, + "fuse_op_lookup_nanos": 139794803, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 122929676, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 29564712, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 44158376, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1197302015, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31143222, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17941, + "fuse_read_lane_wait_nanos": 1712199, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43551, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1852072, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20840, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6818, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14924, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 840089879, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26212, + "attr_cache_misses": 30894, + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32845, + "connection_wait_count": 32852, + "connection_wait_nanos": 11014211, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6400, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30596, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 12067, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 12067, + "fuse_op_getattr_nanos": 71325287, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 139838424, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 122929676, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 31422710, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 44158376, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1197302015, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31143222, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17943, + "fuse_read_lane_wait_nanos": 1713269, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43627, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1852072, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20840, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6818, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26212, + "attr_cache_misses": 30918, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32877, + "connection_wait_count": 32884, + "connection_wait_nanos": 11027937, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6416, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30620, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12083, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 12083, + "fuse_op_getattr_nanos": 71349611, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 139838424, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 123914832, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 31422710, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 44158376, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17951, + "fuse_read_lane_wait_nanos": 1714501, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43667, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1853291, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20856, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6818, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2582410 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26212, + "attr_cache_misses": 30918, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32877, + "connection_wait_count": 32884, + "connection_wait_nanos": 11027937, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 9135, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5402, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 33444, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14802, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12148, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14802, + "fuse_op_getattr_nanos": 72922350, + "fuse_op_lookup_count": 12148, + "fuse_op_lookup_nanos": 139969883, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 123919406, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 31422710, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 44158376, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17951, + "fuse_read_lane_wait_nanos": 1714501, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46503, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1853291, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20856, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2582410 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32892, + "connection_wait_count": 32899, + "connection_wait_nanos": 11043198, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1853291, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2582410 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32895, + "connection_wait_count": 32902, + "connection_wait_nanos": 11049316, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1854144, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2740864 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "agentfs" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32895, + "connection_wait_count": 32902, + "connection_wait_nanos": 11049316, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1854144, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2740864 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "fuse_session" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32895, + "connection_wait_count": 32902, + "connection_wait_nanos": 11049316, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1854144, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2740864 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "run_parent" + } + ], + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-4d8592c65a644b99a57302f36fd3ddff", + "--no-default-allows", + "--", + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base", + "duration_seconds": 4.414166821996332, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 35, + "agentfs_batcher_commit_latency_ns_total": 829850190, + "agentfs_batcher_commit_txns": 4776, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4680, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4730, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9384, + "attr_cache_hits": 25922, + "attr_cache_misses": 29050, + "base_fast_inode_invalidations": 15027, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create_count": 2, + "connection_reuse_count": 31194, + "connection_wait_count": 31196, + "connection_wait_nanos": 10452744, + "dentry_cache_hits": 26031, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 92, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5283, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21782, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 418205, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52456532, + "fuse_flush_count": 4730, + "fuse_flush_ranges": 4730, + "fuse_getattr_count": 5026, + "fuse_ino_file_resolutions": 20, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11700, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 577281862, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 36, + "fuse_op_forget_nanos": 761283, + "fuse_op_getattr_count": 5026, + "fuse_op_getattr_nanos": 53165859, + "fuse_op_lookup_count": 11700, + "fuse_op_lookup_nanos": 135481527, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 784, + "fuse_op_other_nanos": 116756992, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 26555120, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 939247, + "fuse_op_setattr_count": 4748, + "fuse_op_setattr_nanos": 1181414602, + "fuse_op_write_count": 4820, + "fuse_op_write_nanos": 29006612, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16154, + "fuse_read_lane_wait_nanos": 1521801, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32005, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673753, + "fuse_write_count": 4820, + "fuse_write_lane_wait_count": 10203, + "fuse_write_lane_wait_nanos": 1784205, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40292, + "lookup_delta_count": 7193, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6779, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14260, + "path_cache_hits": 26031, + "path_cache_misses": 13426, + "path_component_count": 32908, + "path_resolution_count": 7120, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 833686067, + "agentfs_batcher_commit_txns": 4785, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4685, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26190, + "attr_cache_misses": 30854, + "base_fast_inode_invalidations": 15078, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 983, + "connection_create_count": 7, + "connection_reuse_count": 31819, + "connection_wait_count": 31826, + "connection_wait_nanos": 10711904, + "dentry_cache_hits": 26104, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4670, + "fuse_adapter_attr_misses": 5657, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5295, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27153, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 470550, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10327, + "fuse_ino_file_resolutions": 23, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11749, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 578502015, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 45, + "fuse_op_forget_nanos": 822800, + "fuse_op_getattr_count": 10327, + "fuse_op_getattr_nanos": 69447226, + "fuse_op_lookup_count": 11749, + "fuse_op_lookup_nanos": 135995375, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 799, + "fuse_op_other_nanos": 119602512, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 27525290, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1199042, + "fuse_op_setattr_count": 4762, + "fuse_op_setattr_nanos": 1185456297, + "fuse_op_write_count": 4829, + "fuse_op_write_nanos": 29208930, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16942, + "fuse_read_lane_wait_nanos": 1616199, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37414, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265146, + "fuse_write_count": 4829, + "fuse_write_lane_wait_count": 10245, + "fuse_write_lane_wait_nanos": 1822468, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20808, + "lookup_base_count": 23, + "lookup_count": 40456, + "lookup_delta_count": 7253, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6812, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14346, + "path_cache_hits": 26104, + "path_cache_misses": 13482, + "path_component_count": 33032, + "path_resolution_count": 7165, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 840089879, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26211, + "attr_cache_misses": 30894, + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32837, + "connection_wait_count": 32844, + "connection_wait_nanos": 11003138, + "dentry_cache_hits": 26124, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6331, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30520, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11998, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12042, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 11998, + "fuse_op_getattr_nanos": 71262778, + "fuse_op_lookup_count": 12042, + "fuse_op_lookup_nanos": 139794803, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 122929676, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 29564712, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 44158376, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1197302015, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31143222, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17941, + "fuse_read_lane_wait_nanos": 1712199, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43551, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1852072, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20840, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7548, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6818, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14924, + "path_cache_hits": 26124, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 840089879, + "agentfs_batcher_commit_txns": 4789, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26212, + "attr_cache_misses": 30894, + "base_fast_inode_invalidations": 15102, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32845, + "connection_wait_count": 32852, + "connection_wait_nanos": 11014211, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6400, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30596, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 12067, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 12067, + "fuse_op_getattr_nanos": 71325287, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 139838424, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3561, + "fuse_op_other_nanos": 122929676, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 31422710, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 44158376, + "fuse_op_setattr_count": 4768, + "fuse_op_setattr_nanos": 1197302015, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31143222, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17943, + "fuse_read_lane_wait_nanos": 1713269, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43627, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446604, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10259, + "fuse_write_lane_wait_nanos": 1852072, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20840, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6818, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 1731390 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26212, + "attr_cache_misses": 30918, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32877, + "connection_wait_count": 32884, + "connection_wait_nanos": 11027937, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6416, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5297, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30620, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12083, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12043, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 12083, + "fuse_op_getattr_nanos": 71349611, + "fuse_op_lookup_count": 12043, + "fuse_op_lookup_nanos": 139838424, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3569, + "fuse_op_other_nanos": 123914832, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 31422710, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 44158376, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17951, + "fuse_read_lane_wait_nanos": 1714501, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43667, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1853291, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20856, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6818, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2582410 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26212, + "attr_cache_misses": 30918, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32877, + "connection_wait_count": 32884, + "connection_wait_nanos": 11027937, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 9135, + "fuse_adapter_attr_misses": 5667, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5402, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 33444, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14802, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12148, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14802, + "fuse_op_getattr_nanos": 72922350, + "fuse_op_lookup_count": 12148, + "fuse_op_lookup_nanos": 139969883, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3581, + "fuse_op_other_nanos": 123919406, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 31422710, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 44158376, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17951, + "fuse_read_lane_wait_nanos": 1714501, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46503, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1853291, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20856, + "lookup_base_count": 23, + "lookup_count": 41056, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6923, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14924, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37638, + "path_resolution_count": 8142, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2582410 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32892, + "connection_wait_count": 32899, + "connection_wait_nanos": 11043198, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10267, + "fuse_write_lane_wait_nanos": 1853291, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 2582410 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32895, + "connection_wait_count": 32902, + "connection_wait_nanos": 11049316, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1854144, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2740864 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "agentfs" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32895, + "connection_wait_count": 32902, + "connection_wait_nanos": 11049316, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1854144, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2740864 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "fuse_session" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 41, + "agentfs_batcher_commit_latency_ns_total": 841228959, + "agentfs_batcher_commit_txns": 4797, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 18, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1359872, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26215, + "attr_cache_misses": 30920, + "base_fast_inode_invalidations": 15118, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1003, + "connection_create_count": 7, + "connection_reuse_count": 32895, + "connection_wait_count": 32902, + "connection_wait_nanos": 11049316, + "dentry_cache_hits": 26129, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 9179, + "fuse_adapter_attr_misses": 5668, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5651, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 33762, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 8, + "fuse_dispatch_parallel_tasks": 52, + "fuse_dispatch_wait_count": 52, + "fuse_dispatch_wait_nanos": 552373, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 14847, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12401, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 579599572, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 1285, + "fuse_op_forget_count": 49, + "fuse_op_forget_nanos": 877760, + "fuse_op_getattr_count": 14847, + "fuse_op_getattr_nanos": 72995762, + "fuse_op_lookup_count": 12401, + "fuse_op_lookup_nanos": 140339347, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1039, + "fuse_op_other_count": 3617, + "fuse_op_other_nanos": 123929454, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 33300787, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 44673885, + "fuse_op_setattr_count": 4776, + "fuse_op_setattr_nanos": 1199532741, + "fuse_op_write_count": 4847, + "fuse_op_write_nanos": 31530872, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17966, + "fuse_read_lane_wait_nanos": 1717277, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 46857, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54453002, + "fuse_write_count": 4847, + "fuse_write_lane_wait_count": 10269, + "fuse_write_lane_wait_nanos": 1854144, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20858, + "lookup_base_count": 23, + "lookup_count": 41064, + "lookup_delta_count": 7553, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7172, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14926, + "path_cache_hits": 26129, + "path_cache_misses": 14054, + "path_component_count": 37680, + "path_resolution_count": 8152, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 2740864 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "run_parent" + } + ], + "returncode": 0, + "stderr_bytes": 40585, + "stderr_tail": "ueues\":4754,\"agentfs_batcher_pending_max_bytes\":1359872,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26212,\"attr_cache_misses\":30918,\"base_fast_inode_invalidations\":15118,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":267,\"chunk_read_queries\":122,\"chunk_write_chunks\":1003,\"connection_create_count\":7,\"connection_reuse_count\":32877,\"connection_wait_count\":32884,\"connection_wait_nanos\":11027937,\"dentry_cache_hits\":26125,\"dentry_cache_misses\":14052,\"fuse_adapter_attr_hits\":9135,\"fuse_adapter_attr_misses\":5667,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6734,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5402,\"fuse_adapter_negative_misses\":6734,\"fuse_callback_count\":33444,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":52,\"fuse_dispatch_wait_count\":52,\"fuse_dispatch_wait_nanos\":552373,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":14802,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12148,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":579599572,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":1285,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":877760,\"fuse_op_getattr_count\":14802,\"fuse_op_getattr_nanos\":72922350,\"fuse_op_lookup_count\":12148,\"fuse_op_lookup_nanos\":139969883,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1039,\"fuse_op_other_count\":3581,\"fuse_op_other_nanos\":123919406,\"fuse_op_read_count\":239,\"fuse_op_read_nanos\":31422710,\"fuse_op_readdirplus_count\":1407,\"fuse_op_readdirplus_nanos\":44158376,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1199532741,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":31530872,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":239,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17951,\"fuse_read_lane_wait_nanos\":1714501,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1407,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":46503,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1853291,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20856,\"lookup_base_count\":23,\"lookup_count\":41056,\"lookup_delta_count\":7549,\"lookup_whiteout_count\":0,\"negative_cache_hits\":6923,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46085,\"negative_lookup_count\":14924,\"path_cache_hits\":26125,\"path_cache_misses\":14052,\"path_component_count\":37638,\"path_resolution_count\":8142,\"readdir_count\":1,\"readdir_plus_count\":701,\"wal_checkpoint_count\":11,\"wal_checkpoint_nanos\":2582410},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"phase-checkpoint-6\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":841228959,\"agentfs_batcher_commit_txns\":4797,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4695,\"agentfs_batcher_drains_timer\":18,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1359872,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26215,\"attr_cache_misses\":30920,\"base_fast_inode_invalidations\":15118,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create_count\":7,\"connection_reuse_count\":32892,\"connection_wait_count\":32899,\"connection_wait_nanos\":11043198,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":9179,\"fuse_adapter_attr_misses\":5668,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5651,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":33762,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":52,\"fuse_dispatch_wait_count\":52,\"fuse_dispatch_wait_nanos\":552373,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":14847,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12401,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":579599572,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":1285,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":877760,\"fuse_op_getattr_count\":14847,\"fuse_op_getattr_nanos\":72995762,\"fuse_op_lookup_count\":12401,\"fuse_op_lookup_nanos\":140339347,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1039,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":123929454,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":33300787,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":44673885,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1199532741,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":31530872,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17966,\"fuse_read_lane_wait_nanos\":1717277,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":46857,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10267,\"fuse_write_lane_wait_nanos\":1853291,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20858,\"lookup_base_count\":23,\"lookup_count\":41064,\"lookup_delta_count\":7553,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7172,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14926,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37680,\"path_resolution_count\":8152,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":11,\"wal_checkpoint_nanos\":2582410},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"phase-checkpoint-7\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":841228959,\"agentfs_batcher_commit_txns\":4797,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4695,\"agentfs_batcher_drains_timer\":18,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1359872,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26215,\"attr_cache_misses\":30920,\"base_fast_inode_invalidations\":15118,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create_count\":7,\"connection_reuse_count\":32895,\"connection_wait_count\":32902,\"connection_wait_nanos\":11049316,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":9179,\"fuse_adapter_attr_misses\":5668,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5651,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":33762,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":52,\"fuse_dispatch_wait_count\":52,\"fuse_dispatch_wait_nanos\":552373,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":14847,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12401,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":579599572,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":1285,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":877760,\"fuse_op_getattr_count\":14847,\"fuse_op_getattr_nanos\":72995762,\"fuse_op_lookup_count\":12401,\"fuse_op_lookup_nanos\":140339347,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1039,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":123929454,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":33300787,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":44673885,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1199532741,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":31530872,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17966,\"fuse_read_lane_wait_nanos\":1717277,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":46857,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1854144,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20858,\"lookup_base_count\":23,\"lookup_count\":41064,\"lookup_delta_count\":7553,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7172,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14926,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37680,\"path_resolution_count\":8152,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":2740864},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"agentfs\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":841228959,\"agentfs_batcher_commit_txns\":4797,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4695,\"agentfs_batcher_drains_timer\":18,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1359872,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26215,\"attr_cache_misses\":30920,\"base_fast_inode_invalidations\":15118,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create_count\":7,\"connection_reuse_count\":32895,\"connection_wait_count\":32902,\"connection_wait_nanos\":11049316,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":9179,\"fuse_adapter_attr_misses\":5668,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5651,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":33762,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":52,\"fuse_dispatch_wait_count\":52,\"fuse_dispatch_wait_nanos\":552373,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":14847,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12401,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":579599572,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":1285,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":877760,\"fuse_op_getattr_count\":14847,\"fuse_op_getattr_nanos\":72995762,\"fuse_op_lookup_count\":12401,\"fuse_op_lookup_nanos\":140339347,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1039,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":123929454,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":33300787,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":44673885,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1199532741,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":31530872,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17966,\"fuse_read_lane_wait_nanos\":1717277,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":46857,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1854144,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20858,\"lookup_base_count\":23,\"lookup_count\":41064,\"lookup_delta_count\":7553,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7172,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14926,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37680,\"path_resolution_count\":8152,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":2740864},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"fuse_session\"}\n\nSession: git-workload-4d8592c65a644b99a57302f36fd3ddff\n\nTo resume this session:\n agentfs run --session git-workload-4d8592c65a644b99a57302f36fd3ddff\n\nTo see what changed:\n agentfs diff git-workload-4d8592c65a644b99a57302f36fd3ddff\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":41,\"agentfs_batcher_commit_latency_ns_total\":841228959,\"agentfs_batcher_commit_txns\":4797,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4695,\"agentfs_batcher_drains_timer\":18,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1359872,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26215,\"attr_cache_misses\":30920,\"base_fast_inode_invalidations\":15118,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1003,\"connection_create_count\":7,\"connection_reuse_count\":32895,\"connection_wait_count\":32902,\"connection_wait_nanos\":11049316,\"dentry_cache_hits\":26129,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":9179,\"fuse_adapter_attr_misses\":5668,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5651,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":33762,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":8,\"fuse_dispatch_parallel_tasks\":52,\"fuse_dispatch_wait_count\":52,\"fuse_dispatch_wait_nanos\":552373,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":14847,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12401,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":579599572,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":1285,\"fuse_op_forget_count\":49,\"fuse_op_forget_nanos\":877760,\"fuse_op_getattr_count\":14847,\"fuse_op_getattr_nanos\":72995762,\"fuse_op_lookup_count\":12401,\"fuse_op_lookup_nanos\":140339347,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1039,\"fuse_op_other_count\":3617,\"fuse_op_other_nanos\":123929454,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":33300787,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":44673885,\"fuse_op_setattr_count\":4776,\"fuse_op_setattr_nanos\":1199532741,\"fuse_op_write_count\":4847,\"fuse_op_write_nanos\":31530872,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17966,\"fuse_read_lane_wait_nanos\":1717277,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":46857,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54453002,\"fuse_write_count\":4847,\"fuse_write_lane_wait_count\":10269,\"fuse_write_lane_wait_nanos\":1854144,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20858,\"lookup_base_count\":23,\"lookup_count\":41064,\"lookup_delta_count\":7553,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7172,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14926,\"path_cache_hits\":26129,\"path_cache_misses\":14054,\"path_component_count\":37680,\"path_resolution_count\":8152,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":2740864},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"run_parent\"}\n", + "stdout_bytes": 18988, + "stdout_tail": "2026-07-03T11:39:26.374842Z INFO agentfs::fuser::session: resolved FUSE dispatch mode: parallel workers=7 queue_capacity=28\n2026-07-03T11:39:26.375121Z INFO agentfs::fuse: advertising FUSE_OVER_IO_URING\n2026-07-03T11:39:26.375284Z INFO agentfs::fuser::uring: starting fuse-over-io_uring queues nr_queues=14 depth=4\n2026-07-03T11:39:27.800251Z WARN agentfs::fuser::request: Request RequestId(24209): Failed to send reply: No such file or directory (os error 2)\n2026-07-03T11:39:28.800243Z WARN agentfs::fuser::request: Request RequestId(47729): Failed to send reply: No such file or directory (os error 2)\n{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.09339736700349022, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work\", \"duration_seconds\": 0.03133171200170182, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work\", \"duration_seconds\": 0.030068980006035417, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work\", \"duration_seconds\": 0.03194412399898283, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.0046512179978890345, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work\", \"duration_seconds\": 0.19824458299262915, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work\", \"duration_seconds\": 0.06816758299828507, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/mirror.git\", \"/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base\", \"duration_seconds\": 3.0392134730063844, \"returncode\": 0, \"stderr_bytes\": 2406, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\nUpdating files: 35% (1627/4644)\\nUpdating files: 36% (1672/4644)\\nUpdating files: 37% (1719/4644)\\nUpdating files: 38% (1765/4644)\\nUpdating files: 39% (1812/4644)\\nUpdating files: 40% (1858/4644)\\nUpdating files: 41% (1905/4644)\\nUpdating files: 42% (1951/4644)\\nUpdating files: 43% (1997/4644)\\nUpdating files: 44% (2044/4644)\\nUpdating files: 45% (2090/4644)\\nUpdating files: 46% (2137/4644)\\nUpdating files: 47% (2183/4644)\\nUpdating files: 48% (2230/4644)\\nUpdating files: 49% (2276/4644)\\nUpdating files: 50% (2322/4644)\\nUpdating files: 51% (2369/4644)\\nUpdating files: 52% (2415/4644)\\nUpdating files: 53% (2462/4644)\\nUpdating files: 54% (2508/4644)\\nUpdating files: 55% (2555/4644)\\nUpdating files: 56% (2601/4644)\\nUpdating files: 57% (2648/4644)\\nUpdating files: 58% (2694/4644)\\nUpdating files: 59% (2740/4644)\\nUpdating files: 60% (2787/4644)\\nUpdating files: 61% (2833/4644)\\nUpdating files: 62% (2880/4644)\\nUpdating files: 63% (2926/4644)\\nUpdating files: 64% (2973/4644)\\nUpdating files: 65% (3019/4644)\\nUpdating files: 66% (3066/4644)\\nUpdating files: 67% (3112/4644)\\nUpdating files: 68% (3158/4644)\\nUpdating files: 69% (3205/4644)\\nUpdating files: 70% (3251/4644)\\nUpdating files: 71% (3298/4644)\\nUpdating files: 72% (3344/4644)\\nUpdating files: 73% (3391/4644)\\nUpdating files: 73% (3395/4644)\\nUpdating files: 74% (3437/4644)\\nUpdating files: 75% (3483/4644)\\nUpdating files: 76% (3530/4644)\\nUpdating files: 77% (3576/4644)\\nUpdating files: 78% (3623/4644)\\nUpdating files: 79% (3669/4644)\\nUpdating files: 80% (3716/4644)\\nUpdating files: 81% (3762/4644)\\nUpdating files: 82% (3809/4644)\\nUpdating files: 83% (3855/4644)\\nUpdating files: 84% (3901/4644)\\nUpdating files: 85% (3948/4644)\\nUpdating files: 86% (3994/4644)\\nUpdating files: 87% (4041/4644)\\nUpdating files: 88% (4087/4644)\\nUpdating files: 89% (4134/4644)\\nUpdating files: 90% (4180/4644)\\nUpdating files: 91% (4227/4644)\\nUpdating files: 92% (4273/4644)\\nUpdating files: 93% (4319/4644)\\nUpdating files: 94% (4366/4644)\\nUpdating files: 95% (4412/4644)\\nUpdating files: 96% (4459/4644)\\nUpdating files: 97% (4505/4644)\\nUpdating files: 98% (4552/4644)\\nUpdating files: 99% (4598/4644)\\nUpdating files: 100% (4644/4644)\\nUpdating files: 100% (4644/4644), done.\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work\", \"duration_seconds\": 0.051957800998934545, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work\", \"duration_seconds\": 0.11781196799711324, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.07058002799749374, \"clone\": 3.039248861998203, \"diff\": 0.09339736700349022, \"edit\": 0.0046512179978890345, \"fsck\": 0.19825839099939913, \"read_search\": 0.009133929008385167, \"status\": 0.16980370599776506}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work\", \"duration_seconds\": 0.0061758600058965385, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 4.286349337999127}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.09339736700349022, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work", + "duration_seconds": 0.03133171200170182, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work", + "duration_seconds": 0.030068980006035417, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work", + "duration_seconds": 0.03194412399898283, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.0046512179978890345, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work", + "duration_seconds": 0.19824458299262915, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work", + "duration_seconds": 0.06816758299828507, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/mirror.git", + "/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base", + "duration_seconds": 3.0392134730063844, + "returncode": 0, + "stderr_bytes": 2406, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\nUpdating files: 35% (1627/4644)\nUpdating files: 36% (1672/4644)\nUpdating files: 37% (1719/4644)\nUpdating files: 38% (1765/4644)\nUpdating files: 39% (1812/4644)\nUpdating files: 40% (1858/4644)\nUpdating files: 41% (1905/4644)\nUpdating files: 42% (1951/4644)\nUpdating files: 43% (1997/4644)\nUpdating files: 44% (2044/4644)\nUpdating files: 45% (2090/4644)\nUpdating files: 46% (2137/4644)\nUpdating files: 47% (2183/4644)\nUpdating files: 48% (2230/4644)\nUpdating files: 49% (2276/4644)\nUpdating files: 50% (2322/4644)\nUpdating files: 51% (2369/4644)\nUpdating files: 52% (2415/4644)\nUpdating files: 53% (2462/4644)\nUpdating files: 54% (2508/4644)\nUpdating files: 55% (2555/4644)\nUpdating files: 56% (2601/4644)\nUpdating files: 57% (2648/4644)\nUpdating files: 58% (2694/4644)\nUpdating files: 59% (2740/4644)\nUpdating files: 60% (2787/4644)\nUpdating files: 61% (2833/4644)\nUpdating files: 62% (2880/4644)\nUpdating files: 63% (2926/4644)\nUpdating files: 64% (2973/4644)\nUpdating files: 65% (3019/4644)\nUpdating files: 66% (3066/4644)\nUpdating files: 67% (3112/4644)\nUpdating files: 68% (3158/4644)\nUpdating files: 69% (3205/4644)\nUpdating files: 70% (3251/4644)\nUpdating files: 71% (3298/4644)\nUpdating files: 72% (3344/4644)\nUpdating files: 73% (3391/4644)\nUpdating files: 73% (3395/4644)\nUpdating files: 74% (3437/4644)\nUpdating files: 75% (3483/4644)\nUpdating files: 76% (3530/4644)\nUpdating files: 77% (3576/4644)\nUpdating files: 78% (3623/4644)\nUpdating files: 79% (3669/4644)\nUpdating files: 80% (3716/4644)\nUpdating files: 81% (3762/4644)\nUpdating files: 82% (3809/4644)\nUpdating files: 83% (3855/4644)\nUpdating files: 84% (3901/4644)\nUpdating files: 85% (3948/4644)\nUpdating files: 86% (3994/4644)\nUpdating files: 87% (4041/4644)\nUpdating files: 88% (4087/4644)\nUpdating files: 89% (4134/4644)\nUpdating files: 90% (4180/4644)\nUpdating files: 91% (4227/4644)\nUpdating files: 92% (4273/4644)\nUpdating files: 93% (4319/4644)\nUpdating files: 94% (4366/4644)\nUpdating files: 95% (4412/4644)\nUpdating files: 96% (4459/4644)\nUpdating files: 97% (4505/4644)\nUpdating files: 98% (4552/4644)\nUpdating files: 99% (4598/4644)\nUpdating files: 100% (4644/4644)\nUpdating files: 100% (4644/4644), done.\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work", + "duration_seconds": 0.051957800998934545, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work", + "duration_seconds": 0.11781196799711324, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.07058002799749374, + "clone": 3.039248861998203, + "diff": 0.09339736700349022, + "edit": 0.0046512179978890345, + "fsck": 0.19825839099939913, + "read_search": 0.009133929008385167, + "status": 0.16980370599776506 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/agentfs-base/work", + "duration_seconds": 0.0061758600058965385, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 4.286349337999127 + } + }, + "base_tree": { + "after": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "ce4c65ffc1a49eced925eea30e9a7742b3115215fad4256bc0f6e3084aa5adc1", + "symlinks": 0 + }, + "before": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "ce4c65ffc1a49eced925eea30e9a7742b3115215fad4256bc0f6e3084aa5adc1", + "symlinks": 0 + }, + "unchanged": true + }, + "benchmark": "phase7-git-workload", + "command": { + "agentfs_prefix": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-4d8592c65a644b99a57302f36fd3ddff", + "--no-default-allows", + "--" + ], + "argv": [ + "/home/ain3sh/factory/vfs/scripts/validation/git-workload-benchmark.py", + "--output", + "/home/ain3sh/factory/vfs/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-03.json", + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "workload_argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ] + }, + "correctness": { + "agentfs_backup_verify": true, + "agentfs_base_unchanged": true, + "agentfs_db_inspectable": true, + "agentfs_integrity_require_portable": true, + "agentfs_no_nonempty_sidecars": true, + "agentfs_portable": true, + "agentfs_returncode_zero": true, + "equivalence": { + "agentfs": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + }, + "checked": true, + "equivalent": true, + "native": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + } + }, + "native_returncode_zero": true, + "passed": true, + "performance_passed": false + }, + "database": { + "after": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-pl9ksnf0/home/.agentfs/run/git-workload-4d8592c65a644b99a57302f36fd3ddff/delta.db" + } + ], + "path": "/tmp/agentfs-git-workload-pl9ksnf0/home/.agentfs/run/git-workload-4d8592c65a644b99a57302f36fd3ddff/delta.db", + "total_bytes": 57147392 + }, + "backup": { + "artifacts": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-pl9ksnf0/git-workload-backup.db" + } + ], + "path": "/tmp/agentfs-git-workload-pl9ksnf0/git-workload-backup.db", + "total_bytes": 57147392 + }, + "inspect": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "path": "/tmp/agentfs-git-workload-pl9ksnf0/git-workload-backup.db", + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "backup", + "/tmp/agentfs-git-workload-pl9ksnf0/home/.agentfs/run/git-workload-4d8592c65a644b99a57302f36fd3ddff/delta.db", + "/tmp/agentfs-git-workload-pl9ksnf0/git-workload-backup.db", + "--verify" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0", + "duration_seconds": 2.7827271150017623, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 241, + "stdout_tail": "Source: /tmp/agentfs-git-workload-pl9ksnf0/home/.agentfs/run/git-workload-4d8592c65a644b99a57302f36fd3ddff/delta.db\nBackup: /tmp/agentfs-git-workload-pl9ksnf0/git-workload-backup.db\nCheckpoint: complete\nCopy: complete\nVerification: complete\n", + "timed_out": false + } + }, + "inspect_after": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "integrity": { + "result": { + "checks": [ + { + "detail": "ok", + "name": "pragma.integrity_check", + "ok": true, + "violating_rows": null + }, + { + "detail": "present", + "name": "schema.table.fs_config", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_symlink", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.kv_store", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.tool_calls", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 0.5", + "name": "config.schema_version", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 65536", + "name": "config.chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 16384", + "name": "config.inline_threshold", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.kind_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_has_no_chunks", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunked_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_size_matches_blob", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.non_regular_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_reference_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunk_length_within_chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 1, expected 1", + "name": "namespace.root_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_is_directory", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_target_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_root_inode_has_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_names_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_directory_nlink_matches_dentries", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.directory_nlink_positive", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.rows_reference_symlink_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.inodes_have_rows", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable: no partial-origin rows", + "name": "overlay.portability_status", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable requirement satisfied", + "name": "overlay.require_portable", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_regular", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_sizes_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_paths_absolute", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_references_partial_origin", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_unique", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_index_in_range", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.whiteout_paths_absolute", + "ok": true, + "violating_rows": 0 + } + ], + "database": "/tmp/agentfs-git-workload-pl9ksnf0/home/.agentfs/run/git-workload-4d8592c65a644b99a57302f36fd3ddff/delta.db", + "ok": true, + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true + }, + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "integrity", + "/tmp/agentfs-git-workload-pl9ksnf0/home/.agentfs/run/git-workload-4d8592c65a644b99a57302f36fd3ddff/delta.db", + "--json", + "--require-portable" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0", + "duration_seconds": 2.6693981529970188, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 6395, + "stdout_tail": "{\n \"database\": \"/tmp/agentfs-git-workload-pl9ksnf0/home/.agentfs/run/git-workload-4d8592c65a644b99a57302f36fd3ddff/delta.db\",\n \"ok\": true,\n \"portable\": true,\n \"origin_backed\": false,\n \"partial_origin_rows\": 0,\n \"checks\": [\n {\n \"name\": \"pragma.integrity_check\",\n \"ok\": true,\n \"detail\": \"ok\",\n \"violating_rows\": null\n },\n {\n \"name\": \"schema.table.fs_config\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_inode\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_dentry\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_data\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_symlink\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.kv_store\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.tool_calls\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.schema_version\",\n \"ok\": true,\n \"detail\": \"found 0.5\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.chunk_size\",\n \"ok\": true,\n \"detail\": \"found 65536\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.inline_threshold\",\n \"ok\": true,\n \"detail\": \"found 16384\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.kind_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_has_no_chunks\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunked_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_size_matches_blob\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.non_regular_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_reference_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunk_length_within_chunk_size\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.root_inode\",\n \"ok\": true,\n \"detail\": \"found 1, expected 1\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_is_directory\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_target_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_root_inode_has_dentry\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_names_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_directory_nlink_matches_dentries\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.directory_nlink_positive\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.rows_reference_symlink_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.inodes_have_rows\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.portability_status\",\n \"ok\": true,\n \"detail\": \"portable: no partial-origin rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.require_portable\",\n \"ok\": true,\n \"detail\": \"portable requirement satisfied\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_regular\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_sizes_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_references_partial_origin\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_unique\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_index_in_range\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.whiteout_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n }\n ]\n}\n", + "timed_out": false + } + }, + "nonempty_sidecars": false + }, + "environment": { + "AGENTFS_BIN": "/home/ain3sh/factory/vfs/target/release/agentfs", + "AGENTFS_PROFILE": "1" + }, + "git_commit": "ae1e13fcb1c52d5c57be051610a04aa9a318f2a5", + "kept_temp": false, + "native": { + "run": { + "argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/native", + "duration_seconds": 1.0636102249991382, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 15987, + "stdout_tail": "{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.013877956007490866, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/native/work\", \"duration_seconds\": 0.004502324998611584, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/native/work\", \"duration_seconds\": 0.004704930004663765, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/native/work\", \"duration_seconds\": 0.004625909001333639, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.000580564999836497, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/native/work\", \"duration_seconds\": 0.22032717399997637, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/native/work\", \"duration_seconds\": 0.16584086000511888, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-pl9ksnf0/native/mirror.git\", \"/tmp/agentfs-git-workload-pl9ksnf0/native/work\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/native\", \"duration_seconds\": 0.36232351599028334, \"returncode\": 0, \"stderr_bytes\": 149, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-pl9ksnf0/native/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/native/work\", \"duration_seconds\": 0.12152848600817379, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/native/work\", \"duration_seconds\": 0.10965786900487728, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.16831218400329817, \"clone\": 0.3623605160100851, \"diff\": 0.013877956007490866, \"edit\": 0.000580564999836497, \"fsck\": 0.220335984005942, \"read_search\": 0.006128829001681879, \"status\": 0.2312075390073005}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-pl9ksnf0/native/work\", \"duration_seconds\": 0.0037052320112707093, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 1.0030864370055497}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.013877956007490866, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/native/work", + "duration_seconds": 0.004502324998611584, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/native/work", + "duration_seconds": 0.004704930004663765, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/native/work", + "duration_seconds": 0.004625909001333639, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.000580564999836497, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/native/work", + "duration_seconds": 0.22032717399997637, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/native/work", + "duration_seconds": 0.16584086000511888, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-pl9ksnf0/native/mirror.git", + "/tmp/agentfs-git-workload-pl9ksnf0/native/work" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/native", + "duration_seconds": 0.36232351599028334, + "returncode": 0, + "stderr_bytes": 149, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-pl9ksnf0/native/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/native/work", + "duration_seconds": 0.12152848600817379, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/native/work", + "duration_seconds": 0.10965786900487728, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.16831218400329817, + "clone": 0.3623605160100851, + "diff": 0.013877956007490866, + "edit": 0.000580564999836497, + "fsck": 0.220335984005942, + "read_search": 0.006128829001681879, + "status": 0.2312075390073005 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-pl9ksnf0/native/work", + "duration_seconds": 0.0037052320112707093, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 1.0030864370055497 + } + }, + "parameters": { + "edit_files": 8, + "fixture_dirs": 8, + "fixture_file_size_bytes": 1024, + "fixture_files": 96, + "read_bytes": 4096, + "read_files": 64, + "search_token": "AGENTFS_TOKEN", + "skip_fsck": false, + "timeout_seconds": 180.0 + }, + "schema_version": 1, + "source": { + "kind": "source", + "mirror_head": "7d47056ea42636271ac020b86347fbbef49490aa", + "path": "/home/ain3sh/factory/vfs/.agents/benchmarks/fixtures/codex" + }, + "summary": { + "agentfs_base_unchanged": true, + "agentfs_seconds": 4.286349337999127, + "all_equivalent": true, + "correctness_passed": true, + "native_seconds": 1.0030864370055497, + "passed": true, + "performance_passed": false, + "phase_ratios": { + "checkout": { + "agentfs_seconds": 0.07058002799749374, + "native_seconds": 0.16831218400329817, + "ratio": 0.4193399807355045 + }, + "clone": { + "agentfs_seconds": 3.039248861998203, + "native_seconds": 0.3623605160100851, + "ratio": 8.387362109600307 + }, + "diff": { + "agentfs_seconds": 0.09339736700349022, + "native_seconds": 0.013877956007490866, + "ratio": 6.729907988833325 + }, + "edit": { + "agentfs_seconds": 0.0046512179978890345, + "native_seconds": 0.000580564999836497, + "ratio": 8.011537035816739 + }, + "fsck": { + "agentfs_seconds": 0.19825839099939913, + "native_seconds": 0.220335984005942, + "ratio": 0.8998003294552763 + }, + "read_search": { + "agentfs_seconds": 0.009133929008385167, + "native_seconds": 0.006128829001681879, + "ratio": 1.490322051060427 + }, + "status": { + "agentfs_seconds": 0.16980370599776506, + "native_seconds": 0.2312075390073005, + "ratio": 0.7344211470215226 + } + }, + "ratio": 4.273160497309577, + "threshold_failures": [ + { + "agentfs_seconds": 3.039248861998203, + "native_seconds": 0.3623605160100851, + "phase": "clone", + "ratio": 8.387362109600307 + }, + { + "agentfs_seconds": 0.09339736700349022, + "native_seconds": 0.013877956007490866, + "phase": "diff", + "ratio": 6.729907988833325 + }, + { + "agentfs_seconds": 0.0046512179978890345, + "native_seconds": 0.000580564999836497, + "phase": "edit", + "ratio": 8.011537035816739 + } + ] + }, + "temp_dir": "/tmp/agentfs-git-workload-pl9ksnf0" +} diff --git a/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-04.json b/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-04.json new file mode 100644 index 00000000..94a5214c --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-04.json @@ -0,0 +1,6636 @@ +{ + "agentfs": { + "bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "db_path": "/tmp/agentfs-git-workload-0w61kvk2/home/.agentfs/run/git-workload-b5988d96b4e44e47b6434b7eadaa47f7/delta.db", + "per_phase_counters": { + "checkpoint_count": 7, + "label_count": 7, + "labels_aligned": true, + "phases": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 34, + "agentfs_batcher_commit_latency_ns_total": 1060412259, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4680, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4729, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9383, + "attr_cache_hits": 25921, + "attr_cache_misses": 29051, + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create_count": 2, + "connection_reuse_count": 31215, + "connection_wait_count": 31217, + "connection_wait_nanos": 14314611, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 87, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5403, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21898, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 38, + "fuse_dispatch_wait_count": 38, + "fuse_dispatch_wait_nanos": 312398, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52393244, + "fuse_flush_count": 4729, + "fuse_flush_ranges": 4729, + "fuse_getattr_count": 5021, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11820, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 747979498, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 34, + "fuse_op_forget_nanos": 1958355, + "fuse_op_getattr_count": 5021, + "fuse_op_getattr_nanos": 77611533, + "fuse_op_lookup_count": 11820, + "fuse_op_lookup_nanos": 177704881, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 785, + "fuse_op_other_nanos": 156788141, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 24037637, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 951849, + "fuse_op_setattr_count": 4747, + "fuse_op_setattr_nanos": 1486260631, + "fuse_op_write_count": 4821, + "fuse_op_write_nanos": 44453229, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 3537678, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32120, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673732, + "fuse_write_count": 4821, + "fuse_write_lane_wait_count": 10202, + "fuse_write_lane_wait_nanos": 3282330, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6897, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "phase": "clone", + "seq": 1 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 2, + "agentfs_batcher_commit_latency_ns_total": 3692786, + "agentfs_batcher_commit_txns": 10, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 5, + "agentfs_batcher_drains_timer": 4, + "agentfs_batcher_enqueues": 11, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 16, + "attr_cache_hits": 229, + "attr_cache_misses": 1771, + "base_fast_inode_invalidations": 51, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 16, + "connection_create_count": 7, + "connection_reuse_count": 626, + "connection_wait_count": 633, + "connection_wait_nanos": 349068, + "dentry_cache_hits": 73, + "dentry_cache_misses": 56, + "fuse_adapter_attr_hits": 4598, + "fuse_adapter_attr_misses": 686, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 35, + "fuse_adapter_inval_entry_notifications": 10, + "fuse_adapter_inval_inode_notifications": 28, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 13, + "fuse_adapter_negative_misses": 35, + "fuse_callback_count": 5355, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 5, + "fuse_dispatch_parallel_tasks": 9, + "fuse_dispatch_wait_count": 9, + "fuse_dispatch_wait_nanos": 62632, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 841032, + "fuse_flush_count": 11, + "fuse_flush_ranges": 11, + "fuse_getattr_count": 5284, + "fuse_ino_file_resolutions": 3, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 50, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 9, + "fuse_op_create_nanos": 2360977, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 9, + "fuse_op_forget_nanos": 48028, + "fuse_op_getattr_count": 5284, + "fuse_op_getattr_nanos": 24230900, + "fuse_op_lookup_count": 50, + "fuse_op_lookup_nanos": 629843, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 15, + "fuse_op_other_nanos": 2945248, + "fuse_op_read_count": 8, + "fuse_op_read_nanos": 1738500, + "fuse_op_readdirplus_count": 4, + "fuse_op_readdirplus_nanos": 125688, + "fuse_op_setattr_count": 14, + "fuse_op_setattr_nanos": 3766047, + "fuse_op_write_count": 9, + "fuse_op_write_nanos": 755071, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 8, + "fuse_read_lane_max_concurrent": 4, + "fuse_read_lane_wait_count": 751, + "fuse_read_lane_wait_nanos": 109844, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 4, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5393, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 591393, + "fuse_write_count": 9, + "fuse_write_lane_wait_count": 42, + "fuse_write_lane_wait_nanos": 11698, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 1400, + "lookup_base_count": 0, + "lookup_count": 164, + "lookup_delta_count": 60, + "lookup_whiteout_count": 0, + "negative_cache_hits": 34, + "negative_cache_invalidations": 26, + "negative_cache_misses": 148, + "negative_lookup_count": 86, + "path_cache_hits": 73, + "path_cache_misses": 56, + "path_component_count": 124, + "path_resolution_count": 45, + "readdir_count": 0, + "readdir_plus_count": 2, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "checkout", + "seq": 2 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 4, + "agentfs_batcher_commit_latency_ns_total": 4046041, + "agentfs_batcher_commit_txns": 4, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 2, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 6, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 4, + "attr_cache_hits": 19, + "attr_cache_misses": 38, + "base_fast_inode_invalidations": 24, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 20, + "chunk_read_queries": 12, + "chunk_write_chunks": 20, + "connection_create_count": 0, + "connection_reuse_count": 1018, + "connection_wait_count": 1018, + "connection_wait_nanos": 312607, + "dentry_cache_hits": 20, + "dentry_cache_misses": 570, + "fuse_adapter_attr_hits": 1641, + "fuse_adapter_attr_misses": 8, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 289, + "fuse_adapter_inval_entry_notifications": 4, + "fuse_adapter_inval_inode_notifications": 8, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 3, + "fuse_adapter_negative_misses": 289, + "fuse_callback_count": 3346, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 3, + "fuse_dispatch_wait_count": 3, + "fuse_dispatch_wait_nanos": 38877, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 1181458, + "fuse_flush_count": 6, + "fuse_flush_ranges": 6, + "fuse_getattr_count": 1649, + "fuse_ino_file_resolutions": 2, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 294, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 2, + "fuse_op_create_nanos": 1125451, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 3, + "fuse_op_forget_nanos": 52156, + "fuse_op_getattr_count": 1649, + "fuse_op_getattr_nanos": 1588884, + "fuse_op_lookup_count": 294, + "fuse_op_lookup_nanos": 4375291, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 2762, + "fuse_op_other_nanos": 2468348, + "fuse_op_read_count": 12, + "fuse_op_read_nanos": 2382297, + "fuse_op_readdirplus_count": 1381, + "fuse_op_readdirplus_nanos": 45283486, + "fuse_op_setattr_count": 6, + "fuse_op_setattr_nanos": 6225363, + "fuse_op_write_count": 10, + "fuse_op_write_nanos": 1283055, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 12, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 997, + "fuse_read_lane_wait_nanos": 113007, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1381, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 6116, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 1181458, + "fuse_write_count": 10, + "fuse_write_lane_wait_count": 14, + "fuse_write_lane_wait_nanos": 25265, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 28, + "lookup_base_count": 0, + "lookup_count": 598, + "lookup_delta_count": 295, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7, + "negative_cache_invalidations": 4, + "negative_cache_misses": 873, + "negative_lookup_count": 578, + "path_cache_hits": 20, + "path_cache_misses": 570, + "path_component_count": 4606, + "path_resolution_count": 977, + "readdir_count": 0, + "readdir_plus_count": 688, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "status", + "seq": 3 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 1, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 8, + "connection_wait_count": 8, + "connection_wait_nanos": 7066, + "dentry_cache_hits": 1, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 69, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 1, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 1, + "fuse_callback_count": 76, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 69, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 1, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 69, + "fuse_op_getattr_nanos": 62761, + "fuse_op_lookup_count": 1, + "fuse_op_lookup_nanos": 26698, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 6, + "fuse_op_read_nanos": 1328672, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 6, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 2, + "fuse_read_lane_wait_nanos": 798, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 76, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 2, + "lookup_delta_count": 1, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 2, + "negative_lookup_count": 0, + "path_cache_hits": 1, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "read_search", + "seq": 4 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 1398963, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 8, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 8, + "attr_cache_hits": 0, + "attr_cache_misses": 24, + "base_fast_inode_invalidations": 16, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 32, + "connection_wait_count": 32, + "connection_wait_nanos": 13475, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 16, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 24, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 6398, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 16, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 16, + "fuse_op_getattr_nanos": 25569, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 8, + "fuse_op_other_nanos": 1606824, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 8, + "fuse_op_setattr_nanos": 2470039, + "fuse_op_write_count": 8, + "fuse_op_write_nanos": 489724, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 8, + "fuse_read_lane_wait_nanos": 1341, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 40, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 6398, + "fuse_write_count": 8, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 1288, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 16, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 8, + "wal_checkpoint_nanos": 1460421 + }, + "phase": "edit", + "seq": 5 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 919, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 13, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 932, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 919, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 13, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 919, + "fuse_op_getattr_nanos": 677100, + "fuse_op_lookup_count": 13, + "fuse_op_lookup_nanos": 13502, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 12, + "fuse_op_other_nanos": 6470, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 944, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 13, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "diff", + "seq": 6 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 3, + "attr_cache_misses": 2, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 1, + "chunk_read_queries": 1, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 15, + "connection_wait_count": 15, + "connection_wait_nanos": 17686, + "dentry_cache_hits": 4, + "dentry_cache_misses": 2, + "fuse_adapter_attr_hits": 44, + "fuse_adapter_attr_misses": 1, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 4, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 238, + "fuse_adapter_negative_misses": 4, + "fuse_callback_count": 307, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 45, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 242, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 45, + "fuse_op_getattr_nanos": 93926, + "fuse_op_lookup_count": 242, + "fuse_op_lookup_nanos": 323603, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 36, + "fuse_op_other_nanos": 15638, + "fuse_op_read_count": 2, + "fuse_op_read_nanos": 347036, + "fuse_op_readdirplus_count": 18, + "fuse_op_readdirplus_nanos": 811554, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 2, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 15, + "fuse_read_lane_wait_nanos": 4727, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 18, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 343, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 2, + "lookup_base_count": 0, + "lookup_count": 8, + "lookup_delta_count": 4, + "lookup_whiteout_count": 0, + "negative_cache_hits": 238, + "negative_cache_invalidations": 0, + "negative_cache_misses": 9, + "negative_lookup_count": 2, + "path_cache_hits": 4, + "path_cache_misses": 2, + "path_component_count": 42, + "path_resolution_count": 10, + "readdir_count": 0, + "readdir_plus_count": 9, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "fsck", + "seq": 7 + } + ] + }, + "profile_counters": { + "last_by_source": { + "agentfs": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32917, + "connection_wait_count": 32926, + "connection_wait_nanos": 15021421, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 3321575, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3820303 + }, + "fuse_session": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32917, + "connection_wait_count": 32926, + "connection_wait_nanos": 15021421, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 3321575, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3820303 + }, + "phase-checkpoint-1": { + "agentfs_batcher_coalesced_ranges": 34, + "agentfs_batcher_commit_latency_ns_total": 1060412259, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4680, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4729, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9383, + "attr_cache_hits": 25921, + "attr_cache_misses": 29051, + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create_count": 2, + "connection_reuse_count": 31215, + "connection_wait_count": 31217, + "connection_wait_nanos": 14314611, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 87, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5403, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21898, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 38, + "fuse_dispatch_wait_count": 38, + "fuse_dispatch_wait_nanos": 312398, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52393244, + "fuse_flush_count": 4729, + "fuse_flush_ranges": 4729, + "fuse_getattr_count": 5021, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11820, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 747979498, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 34, + "fuse_op_forget_nanos": 1958355, + "fuse_op_getattr_count": 5021, + "fuse_op_getattr_nanos": 77611533, + "fuse_op_lookup_count": 11820, + "fuse_op_lookup_nanos": 177704881, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 785, + "fuse_op_other_nanos": 156788141, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 24037637, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 951849, + "fuse_op_setattr_count": 4747, + "fuse_op_setattr_nanos": 1486260631, + "fuse_op_write_count": 4821, + "fuse_op_write_nanos": 44453229, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 3537678, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32120, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673732, + "fuse_write_count": 4821, + "fuse_write_lane_wait_count": 10202, + "fuse_write_lane_wait_nanos": 3282330, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6897, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "phase-checkpoint-2": { + "agentfs_batcher_coalesced_ranges": 36, + "agentfs_batcher_commit_latency_ns_total": 1064105045, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4685, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26150, + "attr_cache_misses": 30822, + "base_fast_inode_invalidations": 15079, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 984, + "connection_create_count": 9, + "connection_reuse_count": 31841, + "connection_wait_count": 31850, + "connection_wait_nanos": 14663679, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4685, + "fuse_adapter_attr_misses": 5620, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5416, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27253, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 375030, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10305, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11870, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 750340475, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 2006383, + "fuse_op_getattr_count": 10305, + "fuse_op_getattr_nanos": 101842433, + "fuse_op_lookup_count": 11870, + "fuse_op_lookup_nanos": 178334724, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 800, + "fuse_op_other_nanos": 159733389, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 25776137, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1077537, + "fuse_op_setattr_count": 4761, + "fuse_op_setattr_nanos": 1490026678, + "fuse_op_write_count": 4830, + "fuse_op_write_nanos": 45208300, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16904, + "fuse_read_lane_wait_nanos": 3647522, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37513, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265125, + "fuse_write_count": 4830, + "fuse_write_lane_wait_count": 10244, + "fuse_write_lane_wait_nanos": 3294028, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20734, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6931, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "phase-checkpoint-3": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1068151086, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26169, + "attr_cache_misses": 30860, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32859, + "connection_wait_count": 32868, + "connection_wait_nanos": 14976286, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6326, + "fuse_adapter_attr_misses": 5628, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5419, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30599, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11954, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12164, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 11954, + "fuse_op_getattr_nanos": 103431317, + "fuse_op_lookup_count": 12164, + "fuse_op_lookup_nanos": 182710015, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3562, + "fuse_op_other_nanos": 162201737, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 28158434, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 46361023, + "fuse_op_setattr_count": 4767, + "fuse_op_setattr_nanos": 1496252041, + "fuse_op_write_count": 4840, + "fuse_op_write_nanos": 46491355, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3760529, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43629, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446583, + "fuse_write_count": 4840, + "fuse_write_lane_wait_count": 10258, + "fuse_write_lane_wait_nanos": 3319293, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20762, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6938, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "phase-checkpoint-4": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1068151086, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26170, + "attr_cache_misses": 30860, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32867, + "connection_wait_count": 32876, + "connection_wait_nanos": 14983352, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6395, + "fuse_adapter_attr_misses": 5628, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5419, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30675, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 12023, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12165, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 12023, + "fuse_op_getattr_nanos": 103494078, + "fuse_op_lookup_count": 12165, + "fuse_op_lookup_nanos": 182736713, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3562, + "fuse_op_other_nanos": 162201737, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 29487106, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 46361023, + "fuse_op_setattr_count": 4767, + "fuse_op_setattr_nanos": 1496252041, + "fuse_op_write_count": 4840, + "fuse_op_write_nanos": 46491355, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17903, + "fuse_read_lane_wait_nanos": 3761327, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43705, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446583, + "fuse_write_count": 4840, + "fuse_write_lane_wait_count": 10258, + "fuse_write_lane_wait_nanos": 3319293, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20762, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6938, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "phase-checkpoint-5": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26170, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32899, + "connection_wait_count": 32908, + "connection_wait_nanos": 14996827, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6411, + "fuse_adapter_attr_misses": 5628, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5419, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30699, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12039, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12165, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 12039, + "fuse_op_getattr_nanos": 103519647, + "fuse_op_lookup_count": 12165, + "fuse_op_lookup_nanos": 182736713, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3570, + "fuse_op_other_nanos": 163808561, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 29487106, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 46361023, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17911, + "fuse_read_lane_wait_nanos": 3762668, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43745, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10266, + "fuse_write_lane_wait_nanos": 3320581, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20778, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6938, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3620343 + }, + "phase-checkpoint-6": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26170, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32899, + "connection_wait_count": 32908, + "connection_wait_nanos": 14996827, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7330, + "fuse_adapter_attr_misses": 5628, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5432, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31631, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12958, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12178, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 12958, + "fuse_op_getattr_nanos": 104196747, + "fuse_op_lookup_count": 12178, + "fuse_op_lookup_nanos": 182750215, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3582, + "fuse_op_other_nanos": 163815031, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 29487106, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 46361023, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17911, + "fuse_read_lane_wait_nanos": 3762668, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44689, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10266, + "fuse_write_lane_wait_nanos": 3320581, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20778, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6951, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3620343 + }, + "phase-checkpoint-7": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32914, + "connection_wait_count": 32923, + "connection_wait_nanos": 15014513, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10266, + "fuse_write_lane_wait_nanos": 3320581, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3620343 + }, + "run_parent": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32917, + "connection_wait_count": 32926, + "connection_wait_nanos": 15021421, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 3321575, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3820303 + } + }, + "max_counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32917, + "connection_wait_count": 32926, + "connection_wait_nanos": 15021421, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 3321575, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3820303 + }, + "summary_count": 10 + }, + "profile_enabled": true, + "profile_summary_count": 10, + "session": "git-workload-b5988d96b4e44e47b6434b7eadaa47f7" + }, + "agentfs_overlay": { + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 34, + "agentfs_batcher_commit_latency_ns_total": 1060412259, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4680, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4729, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9383, + "attr_cache_hits": 25921, + "attr_cache_misses": 29051, + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create_count": 2, + "connection_reuse_count": 31215, + "connection_wait_count": 31217, + "connection_wait_nanos": 14314611, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 87, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5403, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21898, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 38, + "fuse_dispatch_wait_count": 38, + "fuse_dispatch_wait_nanos": 312398, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52393244, + "fuse_flush_count": 4729, + "fuse_flush_ranges": 4729, + "fuse_getattr_count": 5021, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11820, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 747979498, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 34, + "fuse_op_forget_nanos": 1958355, + "fuse_op_getattr_count": 5021, + "fuse_op_getattr_nanos": 77611533, + "fuse_op_lookup_count": 11820, + "fuse_op_lookup_nanos": 177704881, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 785, + "fuse_op_other_nanos": 156788141, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 24037637, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 951849, + "fuse_op_setattr_count": 4747, + "fuse_op_setattr_nanos": 1486260631, + "fuse_op_write_count": 4821, + "fuse_op_write_nanos": 44453229, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 3537678, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32120, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673732, + "fuse_write_count": 4821, + "fuse_write_lane_wait_count": 10202, + "fuse_write_lane_wait_nanos": 3282330, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6897, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 36, + "agentfs_batcher_commit_latency_ns_total": 1064105045, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4685, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26150, + "attr_cache_misses": 30822, + "base_fast_inode_invalidations": 15079, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 984, + "connection_create_count": 9, + "connection_reuse_count": 31841, + "connection_wait_count": 31850, + "connection_wait_nanos": 14663679, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4685, + "fuse_adapter_attr_misses": 5620, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5416, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27253, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 375030, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10305, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11870, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 750340475, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 2006383, + "fuse_op_getattr_count": 10305, + "fuse_op_getattr_nanos": 101842433, + "fuse_op_lookup_count": 11870, + "fuse_op_lookup_nanos": 178334724, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 800, + "fuse_op_other_nanos": 159733389, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 25776137, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1077537, + "fuse_op_setattr_count": 4761, + "fuse_op_setattr_nanos": 1490026678, + "fuse_op_write_count": 4830, + "fuse_op_write_nanos": 45208300, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16904, + "fuse_read_lane_wait_nanos": 3647522, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37513, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265125, + "fuse_write_count": 4830, + "fuse_write_lane_wait_count": 10244, + "fuse_write_lane_wait_nanos": 3294028, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20734, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6931, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1068151086, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26169, + "attr_cache_misses": 30860, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32859, + "connection_wait_count": 32868, + "connection_wait_nanos": 14976286, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6326, + "fuse_adapter_attr_misses": 5628, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5419, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30599, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11954, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12164, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 11954, + "fuse_op_getattr_nanos": 103431317, + "fuse_op_lookup_count": 12164, + "fuse_op_lookup_nanos": 182710015, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3562, + "fuse_op_other_nanos": 162201737, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 28158434, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 46361023, + "fuse_op_setattr_count": 4767, + "fuse_op_setattr_nanos": 1496252041, + "fuse_op_write_count": 4840, + "fuse_op_write_nanos": 46491355, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3760529, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43629, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446583, + "fuse_write_count": 4840, + "fuse_write_lane_wait_count": 10258, + "fuse_write_lane_wait_nanos": 3319293, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20762, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6938, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1068151086, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26170, + "attr_cache_misses": 30860, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32867, + "connection_wait_count": 32876, + "connection_wait_nanos": 14983352, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6395, + "fuse_adapter_attr_misses": 5628, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5419, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30675, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 12023, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12165, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 12023, + "fuse_op_getattr_nanos": 103494078, + "fuse_op_lookup_count": 12165, + "fuse_op_lookup_nanos": 182736713, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3562, + "fuse_op_other_nanos": 162201737, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 29487106, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 46361023, + "fuse_op_setattr_count": 4767, + "fuse_op_setattr_nanos": 1496252041, + "fuse_op_write_count": 4840, + "fuse_op_write_nanos": 46491355, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17903, + "fuse_read_lane_wait_nanos": 3761327, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43705, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446583, + "fuse_write_count": 4840, + "fuse_write_lane_wait_count": 10258, + "fuse_write_lane_wait_nanos": 3319293, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20762, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6938, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26170, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32899, + "connection_wait_count": 32908, + "connection_wait_nanos": 14996827, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6411, + "fuse_adapter_attr_misses": 5628, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5419, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30699, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12039, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12165, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 12039, + "fuse_op_getattr_nanos": 103519647, + "fuse_op_lookup_count": 12165, + "fuse_op_lookup_nanos": 182736713, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3570, + "fuse_op_other_nanos": 163808561, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 29487106, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 46361023, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17911, + "fuse_read_lane_wait_nanos": 3762668, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43745, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10266, + "fuse_write_lane_wait_nanos": 3320581, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20778, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6938, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3620343 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26170, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32899, + "connection_wait_count": 32908, + "connection_wait_nanos": 14996827, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7330, + "fuse_adapter_attr_misses": 5628, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5432, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31631, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12958, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12178, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 12958, + "fuse_op_getattr_nanos": 104196747, + "fuse_op_lookup_count": 12178, + "fuse_op_lookup_nanos": 182750215, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3582, + "fuse_op_other_nanos": 163815031, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 29487106, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 46361023, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17911, + "fuse_read_lane_wait_nanos": 3762668, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44689, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10266, + "fuse_write_lane_wait_nanos": 3320581, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20778, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6951, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3620343 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32914, + "connection_wait_count": 32923, + "connection_wait_nanos": 15014513, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10266, + "fuse_write_lane_wait_nanos": 3320581, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3620343 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32917, + "connection_wait_count": 32926, + "connection_wait_nanos": 15021421, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 3321575, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3820303 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "agentfs" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32917, + "connection_wait_count": 32926, + "connection_wait_nanos": 15021421, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 3321575, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3820303 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "fuse_session" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32917, + "connection_wait_count": 32926, + "connection_wait_nanos": 15021421, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 3321575, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3820303 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "run_parent" + } + ], + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-b5988d96b4e44e47b6434b7eadaa47f7", + "--no-default-allows", + "--", + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/agentfs-base", + "duration_seconds": 5.065666067006532, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 34, + "agentfs_batcher_commit_latency_ns_total": 1060412259, + "agentfs_batcher_commit_txns": 4794, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4680, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4729, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9383, + "attr_cache_hits": 25921, + "attr_cache_misses": 29051, + "base_fast_inode_invalidations": 15028, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 968, + "connection_create_count": 2, + "connection_reuse_count": 31215, + "connection_wait_count": 31217, + "connection_wait_nanos": 14314611, + "dentry_cache_hits": 26032, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 87, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5403, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21898, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 38, + "fuse_dispatch_wait_count": 38, + "fuse_dispatch_wait_nanos": 312398, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52393244, + "fuse_flush_count": 4729, + "fuse_flush_ranges": 4729, + "fuse_getattr_count": 5021, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11820, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 747979498, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 34, + "fuse_op_forget_nanos": 1958355, + "fuse_op_getattr_count": 5021, + "fuse_op_getattr_nanos": 77611533, + "fuse_op_lookup_count": 11820, + "fuse_op_lookup_nanos": 177704881, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 785, + "fuse_op_other_nanos": 156788141, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 24037637, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 951849, + "fuse_op_setattr_count": 4747, + "fuse_op_setattr_nanos": 1486260631, + "fuse_op_write_count": 4821, + "fuse_op_write_nanos": 44453229, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16153, + "fuse_read_lane_wait_nanos": 3537678, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32120, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673732, + "fuse_write_count": 4821, + "fuse_write_lane_wait_count": 10202, + "fuse_write_lane_wait_nanos": 3282330, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19334, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6897, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26032, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 36, + "agentfs_batcher_commit_latency_ns_total": 1064105045, + "agentfs_batcher_commit_txns": 4804, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4685, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4740, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26150, + "attr_cache_misses": 30822, + "base_fast_inode_invalidations": 15079, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 984, + "connection_create_count": 9, + "connection_reuse_count": 31841, + "connection_wait_count": 31850, + "connection_wait_nanos": 14663679, + "dentry_cache_hits": 26105, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4685, + "fuse_adapter_attr_misses": 5620, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5416, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27253, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 47, + "fuse_dispatch_wait_count": 47, + "fuse_dispatch_wait_nanos": 375030, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4740, + "fuse_flush_ranges": 4740, + "fuse_getattr_count": 10305, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11870, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 750340475, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 43, + "fuse_op_forget_nanos": 2006383, + "fuse_op_getattr_count": 10305, + "fuse_op_getattr_nanos": 101842433, + "fuse_op_lookup_count": 11870, + "fuse_op_lookup_nanos": 178334724, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 800, + "fuse_op_other_nanos": 159733389, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 25776137, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1077537, + "fuse_op_setattr_count": 4761, + "fuse_op_setattr_nanos": 1490026678, + "fuse_op_write_count": 4830, + "fuse_op_write_nanos": 45208300, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 16904, + "fuse_read_lane_wait_nanos": 3647522, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37513, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265125, + "fuse_write_count": 4830, + "fuse_write_lane_wait_count": 10244, + "fuse_write_lane_wait_nanos": 3294028, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20734, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6931, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26105, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1068151086, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26169, + "attr_cache_misses": 30860, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 257, + "chunk_read_queries": 116, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32859, + "connection_wait_count": 32868, + "connection_wait_nanos": 14976286, + "dentry_cache_hits": 26125, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6326, + "fuse_adapter_attr_misses": 5628, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6733, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5419, + "fuse_adapter_negative_misses": 6733, + "fuse_callback_count": 30599, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 11954, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12164, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 11954, + "fuse_op_getattr_nanos": 103431317, + "fuse_op_lookup_count": 12164, + "fuse_op_lookup_nanos": 182710015, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3562, + "fuse_op_other_nanos": 162201737, + "fuse_op_read_count": 233, + "fuse_op_read_nanos": 28158434, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 46361023, + "fuse_op_setattr_count": 4767, + "fuse_op_setattr_nanos": 1496252041, + "fuse_op_write_count": 4840, + "fuse_op_write_nanos": 46491355, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 233, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17901, + "fuse_read_lane_wait_nanos": 3760529, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43629, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446583, + "fuse_write_count": 4840, + "fuse_write_lane_wait_count": 10258, + "fuse_write_lane_wait_nanos": 3319293, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20762, + "lookup_base_count": 23, + "lookup_count": 41050, + "lookup_delta_count": 7546, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6938, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46083, + "negative_lookup_count": 14920, + "path_cache_hits": 26125, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1068151086, + "agentfs_batcher_commit_txns": 4808, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4746, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9403, + "attr_cache_hits": 26170, + "attr_cache_misses": 30860, + "base_fast_inode_invalidations": 15103, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32867, + "connection_wait_count": 32876, + "connection_wait_nanos": 14983352, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6395, + "fuse_adapter_attr_misses": 5628, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5419, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30675, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54415734, + "fuse_flush_count": 4746, + "fuse_flush_ranges": 4746, + "fuse_getattr_count": 12023, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12165, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 12023, + "fuse_op_getattr_nanos": 103494078, + "fuse_op_lookup_count": 12165, + "fuse_op_lookup_nanos": 182736713, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3562, + "fuse_op_other_nanos": 162201737, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 29487106, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 46361023, + "fuse_op_setattr_count": 4767, + "fuse_op_setattr_nanos": 1496252041, + "fuse_op_write_count": 4840, + "fuse_op_write_nanos": 46491355, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17903, + "fuse_read_lane_wait_nanos": 3761327, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43705, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54446583, + "fuse_write_count": 4840, + "fuse_write_lane_wait_count": 10258, + "fuse_write_lane_wait_nanos": 3319293, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20762, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6938, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 2159922 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26170, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32899, + "connection_wait_count": 32908, + "connection_wait_nanos": 14996827, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 6411, + "fuse_adapter_attr_misses": 5628, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5419, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 30699, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12039, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12165, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 12039, + "fuse_op_getattr_nanos": 103519647, + "fuse_op_lookup_count": 12165, + "fuse_op_lookup_nanos": 182736713, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3570, + "fuse_op_other_nanos": 163808561, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 29487106, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 46361023, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17911, + "fuse_read_lane_wait_nanos": 3762668, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 43745, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10266, + "fuse_write_lane_wait_nanos": 3320581, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20778, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6938, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3620343 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26170, + "attr_cache_misses": 30884, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 267, + "chunk_read_queries": 122, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32899, + "connection_wait_count": 32908, + "connection_wait_nanos": 14996827, + "dentry_cache_hits": 26126, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7330, + "fuse_adapter_attr_misses": 5628, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6734, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5432, + "fuse_adapter_negative_misses": 6734, + "fuse_callback_count": 31631, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 12958, + "fuse_ino_file_resolutions": 27, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12178, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 12958, + "fuse_op_getattr_nanos": 104196747, + "fuse_op_lookup_count": 12178, + "fuse_op_lookup_nanos": 182750215, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3582, + "fuse_op_other_nanos": 163815031, + "fuse_op_read_count": 239, + "fuse_op_read_nanos": 29487106, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 46361023, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 239, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17911, + "fuse_read_lane_wait_nanos": 3762668, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44689, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10266, + "fuse_write_lane_wait_nanos": 3320581, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20778, + "lookup_base_count": 23, + "lookup_count": 41052, + "lookup_delta_count": 7547, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6951, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46085, + "negative_lookup_count": 14920, + "path_cache_hits": 26126, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3620343 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32914, + "connection_wait_count": 32923, + "connection_wait_nanos": 15014513, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10266, + "fuse_write_lane_wait_nanos": 3320581, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 3620343 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32917, + "connection_wait_count": 32926, + "connection_wait_nanos": 15021421, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 3321575, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3820303 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "agentfs" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32917, + "connection_wait_count": 32926, + "connection_wait_nanos": 15021421, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 3321575, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3820303 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "fuse_session" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 40, + "agentfs_batcher_commit_latency_ns_total": 1069550049, + "agentfs_batcher_commit_txns": 4816, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 19, + "agentfs_batcher_enqueues": 4754, + "agentfs_batcher_pending_max_bytes": 1343488, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9411, + "attr_cache_hits": 26173, + "attr_cache_misses": 30886, + "base_fast_inode_invalidations": 15119, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 268, + "chunk_read_queries": 123, + "chunk_write_chunks": 1004, + "connection_create_count": 9, + "connection_reuse_count": 32917, + "connection_wait_count": 32926, + "connection_wait_nanos": 15021421, + "dentry_cache_hits": 26130, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7374, + "fuse_adapter_attr_misses": 5629, + "fuse_adapter_entry_hits": 12, + "fuse_adapter_entry_misses": 6738, + "fuse_adapter_inval_entry_notifications": 60, + "fuse_adapter_inval_inode_notifications": 5508, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5670, + "fuse_adapter_negative_misses": 6738, + "fuse_callback_count": 31938, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 413907, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 54422132, + "fuse_flush_count": 4754, + "fuse_flush_ranges": 4754, + "fuse_getattr_count": 13003, + "fuse_ino_file_resolutions": 28, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12420, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 751465926, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 819, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 2058539, + "fuse_op_getattr_count": 13003, + "fuse_op_getattr_nanos": 104290673, + "fuse_op_lookup_count": 12420, + "fuse_op_lookup_nanos": 183073818, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 1108, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 163830669, + "fuse_op_read_count": 241, + "fuse_op_read_nanos": 29834142, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 47172577, + "fuse_op_setattr_count": 4775, + "fuse_op_setattr_nanos": 1498722080, + "fuse_op_write_count": 4848, + "fuse_op_write_nanos": 46981079, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 241, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 17926, + "fuse_read_lane_wait_nanos": 3767395, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 45032, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 54452981, + "fuse_write_count": 4848, + "fuse_write_lane_wait_count": 10268, + "fuse_write_lane_wait_nanos": 3321575, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20780, + "lookup_base_count": 23, + "lookup_count": 41060, + "lookup_delta_count": 7551, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7189, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46094, + "negative_lookup_count": 14922, + "path_cache_hits": 26130, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 3820303 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "run_parent" + } + ], + "returncode": 0, + "stderr_bytes": 40604, + "stderr_tail": "54,\"agentfs_batcher_pending_max_bytes\":1343488,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26170,\"attr_cache_misses\":30884,\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":267,\"chunk_read_queries\":122,\"chunk_write_chunks\":1004,\"connection_create_count\":9,\"connection_reuse_count\":32899,\"connection_wait_count\":32908,\"connection_wait_nanos\":14996827,\"dentry_cache_hits\":26126,\"dentry_cache_misses\":14052,\"fuse_adapter_attr_hits\":7330,\"fuse_adapter_attr_misses\":5628,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6734,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5432,\"fuse_adapter_negative_misses\":6734,\"fuse_callback_count\":31631,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":50,\"fuse_dispatch_wait_count\":50,\"fuse_dispatch_wait_nanos\":413907,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":12958,\"fuse_ino_file_resolutions\":27,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12178,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":751465926,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":819,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":2058539,\"fuse_op_getattr_count\":12958,\"fuse_op_getattr_nanos\":104196747,\"fuse_op_lookup_count\":12178,\"fuse_op_lookup_nanos\":182750215,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1108,\"fuse_op_other_count\":3582,\"fuse_op_other_nanos\":163815031,\"fuse_op_read_count\":239,\"fuse_op_read_nanos\":29487106,\"fuse_op_readdirplus_count\":1407,\"fuse_op_readdirplus_nanos\":46361023,\"fuse_op_setattr_count\":4775,\"fuse_op_setattr_nanos\":1498722080,\"fuse_op_write_count\":4848,\"fuse_op_write_nanos\":46981079,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":239,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17911,\"fuse_read_lane_wait_nanos\":3762668,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1407,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":44689,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452981,\"fuse_write_count\":4848,\"fuse_write_lane_wait_count\":10266,\"fuse_write_lane_wait_nanos\":3320581,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20778,\"lookup_base_count\":23,\"lookup_count\":41052,\"lookup_delta_count\":7547,\"lookup_whiteout_count\":0,\"negative_cache_hits\":6951,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46085,\"negative_lookup_count\":14920,\"path_cache_hits\":26126,\"path_cache_misses\":14052,\"path_component_count\":37632,\"path_resolution_count\":8140,\"readdir_count\":1,\"readdir_plus_count\":701,\"wal_checkpoint_count\":11,\"wal_checkpoint_nanos\":3620343},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"phase-checkpoint-6\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":40,\"agentfs_batcher_commit_latency_ns_total\":1069550049,\"agentfs_batcher_commit_txns\":4816,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4695,\"agentfs_batcher_drains_timer\":19,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1343488,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26173,\"attr_cache_misses\":30886,\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1004,\"connection_create_count\":9,\"connection_reuse_count\":32914,\"connection_wait_count\":32923,\"connection_wait_nanos\":15014513,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7374,\"fuse_adapter_attr_misses\":5629,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5670,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":31938,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":50,\"fuse_dispatch_wait_count\":50,\"fuse_dispatch_wait_nanos\":413907,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13003,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12420,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":751465926,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":819,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":2058539,\"fuse_op_getattr_count\":13003,\"fuse_op_getattr_nanos\":104290673,\"fuse_op_lookup_count\":12420,\"fuse_op_lookup_nanos\":183073818,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1108,\"fuse_op_other_count\":3618,\"fuse_op_other_nanos\":163830669,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":29834142,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":47172577,\"fuse_op_setattr_count\":4775,\"fuse_op_setattr_nanos\":1498722080,\"fuse_op_write_count\":4848,\"fuse_op_write_nanos\":46981079,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17926,\"fuse_read_lane_wait_nanos\":3767395,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45032,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452981,\"fuse_write_count\":4848,\"fuse_write_lane_wait_count\":10266,\"fuse_write_lane_wait_nanos\":3320581,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20780,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7189,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":11,\"wal_checkpoint_nanos\":3620343},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"phase-checkpoint-7\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":40,\"agentfs_batcher_commit_latency_ns_total\":1069550049,\"agentfs_batcher_commit_txns\":4816,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4695,\"agentfs_batcher_drains_timer\":19,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1343488,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26173,\"attr_cache_misses\":30886,\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1004,\"connection_create_count\":9,\"connection_reuse_count\":32917,\"connection_wait_count\":32926,\"connection_wait_nanos\":15021421,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7374,\"fuse_adapter_attr_misses\":5629,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5670,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":31938,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":50,\"fuse_dispatch_wait_count\":50,\"fuse_dispatch_wait_nanos\":413907,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13003,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12420,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":751465926,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":819,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":2058539,\"fuse_op_getattr_count\":13003,\"fuse_op_getattr_nanos\":104290673,\"fuse_op_lookup_count\":12420,\"fuse_op_lookup_nanos\":183073818,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1108,\"fuse_op_other_count\":3618,\"fuse_op_other_nanos\":163830669,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":29834142,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":47172577,\"fuse_op_setattr_count\":4775,\"fuse_op_setattr_nanos\":1498722080,\"fuse_op_write_count\":4848,\"fuse_op_write_nanos\":46981079,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17926,\"fuse_read_lane_wait_nanos\":3767395,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45032,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452981,\"fuse_write_count\":4848,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":3321575,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20780,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7189,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":3820303},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"agentfs\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":40,\"agentfs_batcher_commit_latency_ns_total\":1069550049,\"agentfs_batcher_commit_txns\":4816,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4695,\"agentfs_batcher_drains_timer\":19,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1343488,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26173,\"attr_cache_misses\":30886,\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1004,\"connection_create_count\":9,\"connection_reuse_count\":32917,\"connection_wait_count\":32926,\"connection_wait_nanos\":15021421,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7374,\"fuse_adapter_attr_misses\":5629,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5670,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":31938,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":50,\"fuse_dispatch_wait_count\":50,\"fuse_dispatch_wait_nanos\":413907,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13003,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12420,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":751465926,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":819,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":2058539,\"fuse_op_getattr_count\":13003,\"fuse_op_getattr_nanos\":104290673,\"fuse_op_lookup_count\":12420,\"fuse_op_lookup_nanos\":183073818,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1108,\"fuse_op_other_count\":3618,\"fuse_op_other_nanos\":163830669,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":29834142,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":47172577,\"fuse_op_setattr_count\":4775,\"fuse_op_setattr_nanos\":1498722080,\"fuse_op_write_count\":4848,\"fuse_op_write_nanos\":46981079,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17926,\"fuse_read_lane_wait_nanos\":3767395,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45032,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452981,\"fuse_write_count\":4848,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":3321575,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20780,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7189,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":3820303},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"fuse_session\"}\n\nSession: git-workload-b5988d96b4e44e47b6434b7eadaa47f7\n\nTo resume this session:\n agentfs run --session git-workload-b5988d96b4e44e47b6434b7eadaa47f7\n\nTo see what changed:\n agentfs diff git-workload-b5988d96b4e44e47b6434b7eadaa47f7\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":40,\"agentfs_batcher_commit_latency_ns_total\":1069550049,\"agentfs_batcher_commit_txns\":4816,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4695,\"agentfs_batcher_drains_timer\":19,\"agentfs_batcher_enqueues\":4754,\"agentfs_batcher_pending_max_bytes\":1343488,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9411,\"attr_cache_hits\":26173,\"attr_cache_misses\":30886,\"base_fast_inode_invalidations\":15119,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":268,\"chunk_read_queries\":123,\"chunk_write_chunks\":1004,\"connection_create_count\":9,\"connection_reuse_count\":32917,\"connection_wait_count\":32926,\"connection_wait_nanos\":15021421,\"dentry_cache_hits\":26130,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7374,\"fuse_adapter_attr_misses\":5629,\"fuse_adapter_entry_hits\":12,\"fuse_adapter_entry_misses\":6738,\"fuse_adapter_inval_entry_notifications\":60,\"fuse_adapter_inval_inode_notifications\":5508,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5670,\"fuse_adapter_negative_misses\":6738,\"fuse_callback_count\":31938,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":50,\"fuse_dispatch_wait_count\":50,\"fuse_dispatch_wait_nanos\":413907,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":54422132,\"fuse_flush_count\":4754,\"fuse_flush_ranges\":4754,\"fuse_getattr_count\":13003,\"fuse_ino_file_resolutions\":28,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12420,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":751465926,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":819,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":2058539,\"fuse_op_getattr_count\":13003,\"fuse_op_getattr_nanos\":104290673,\"fuse_op_lookup_count\":12420,\"fuse_op_lookup_nanos\":183073818,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":1108,\"fuse_op_other_count\":3618,\"fuse_op_other_nanos\":163830669,\"fuse_op_read_count\":241,\"fuse_op_read_nanos\":29834142,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":47172577,\"fuse_op_setattr_count\":4775,\"fuse_op_setattr_nanos\":1498722080,\"fuse_op_write_count\":4848,\"fuse_op_write_nanos\":46981079,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":241,\"fuse_read_lane_max_concurrent\":5,\"fuse_read_lane_wait_count\":17926,\"fuse_read_lane_wait_nanos\":3767395,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":45032,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":54452981,\"fuse_write_count\":4848,\"fuse_write_lane_wait_count\":10268,\"fuse_write_lane_wait_nanos\":3321575,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20780,\"lookup_base_count\":23,\"lookup_count\":41060,\"lookup_delta_count\":7551,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7189,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46094,\"negative_lookup_count\":14922,\"path_cache_hits\":26130,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":3820303},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"run_parent\"}\n", + "stdout_bytes": 19368, + "stdout_tail": "2026-07-03T11:39:37.783661Z INFO agentfs::fuser::session: resolved FUSE dispatch mode: parallel workers=7 queue_capacity=28\n2026-07-03T11:39:37.783896Z INFO agentfs::fuse: advertising FUSE_OVER_IO_URING\n2026-07-03T11:39:37.784141Z INFO agentfs::fuser::uring: starting fuse-over-io_uring queues nr_queues=14 depth=4\n2026-07-03T11:39:39.165357Z WARN agentfs::fuser::request: Request RequestId(20345): Failed to send reply: No such file or directory (os error 2)\n2026-07-03T11:39:40.165378Z WARN agentfs::fuser::request: Request RequestId(37581): Failed to send reply: No such file or directory (os error 2)\n2026-07-03T11:39:41.165361Z WARN agentfs::fuser::request: Request RequestId(55747): Failed to send reply: No such file or directory (os error 2)\n{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.0205535150016658, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work\", \"duration_seconds\": 0.009685308003099635, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work\", \"duration_seconds\": 0.005176992999622598, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work\", \"duration_seconds\": 0.005619338000542484, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.006582002999493852, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work\", \"duration_seconds\": 0.22152803500648588, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work\", \"duration_seconds\": 0.07085387800179888, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/mirror.git\", \"/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/agentfs-base\", \"duration_seconds\": 3.7440430769929662, \"returncode\": 0, \"stderr_bytes\": 2637, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\nUpdating files: 29% (1349/4644)\\nUpdating files: 30% (1394/4644)\\nUpdating files: 31% (1440/4644)\\nUpdating files: 32% (1487/4644)\\nUpdating files: 33% (1533/4644)\\nUpdating files: 34% (1579/4644)\\nUpdating files: 35% (1626/4644)\\nUpdating files: 36% (1672/4644)\\nUpdating files: 37% (1719/4644)\\nUpdating files: 38% (1765/4644)\\nUpdating files: 39% (1812/4644)\\nUpdating files: 40% (1858/4644)\\nUpdating files: 41% (1905/4644)\\nUpdating files: 42% (1951/4644)\\nUpdating files: 43% (1997/4644)\\nUpdating files: 44% (2044/4644)\\nUpdating files: 45% (2090/4644)\\nUpdating files: 46% (2137/4644)\\nUpdating files: 47% (2183/4644)\\nUpdating files: 48% (2230/4644)\\nUpdating files: 49% (2276/4644)\\nUpdating files: 50% (2322/4644)\\nUpdating files: 51% (2369/4644)\\nUpdating files: 52% (2415/4644)\\nUpdating files: 53% (2462/4644)\\nUpdating files: 54% (2508/4644)\\nUpdating files: 55% (2555/4644)\\nUpdating files: 56% (2601/4644)\\nUpdating files: 56% (2627/4644)\\nUpdating files: 57% (2648/4644)\\nUpdating files: 58% (2694/4644)\\nUpdating files: 59% (2740/4644)\\nUpdating files: 60% (2787/4644)\\nUpdating files: 61% (2833/4644)\\nUpdating files: 62% (2880/4644)\\nUpdating files: 63% (2926/4644)\\nUpdating files: 64% (2973/4644)\\nUpdating files: 65% (3019/4644)\\nUpdating files: 66% (3066/4644)\\nUpdating files: 67% (3112/4644)\\nUpdating files: 68% (3158/4644)\\nUpdating files: 69% (3205/4644)\\nUpdating files: 70% (3251/4644)\\nUpdating files: 71% (3298/4644)\\nUpdating files: 72% (3344/4644)\\nUpdating files: 73% (3391/4644)\\nUpdating files: 74% (3437/4644)\\nUpdating files: 75% (3483/4644)\\nUpdating files: 76% (3530/4644)\\nUpdating files: 77% (3576/4644)\\nUpdating files: 78% (3623/4644)\\nUpdating files: 79% (3669/4644)\\nUpdating files: 80% (3716/4644)\\nUpdating files: 81% (3762/4644)\\nUpdating files: 82% (3809/4644)\\nUpdating files: 83% (3855/4644)\\nUpdating files: 84% (3901/4644)\\nUpdating files: 85% (3948/4644)\\nUpdating files: 86% (3994/4644)\\nUpdating files: 86% (4025/4644)\\nUpdating files: 87% (4041/4644)\\nUpdating files: 88% (4087/4644)\\nUpdating files: 89% (4134/4644)\\nUpdating files: 90% (4180/4644)\\nUpdating files: 91% (4227/4644)\\nUpdating files: 92% (4273/4644)\\nUpdating files: 93% (4319/4644)\\nUpdating files: 94% (4366/4644)\\nUpdating files: 95% (4412/4644)\\nUpdating files: 96% (4459/4644)\\nUpdating files: 97% (4505/4644)\\nUpdating files: 98% (4552/4644)\\nUpdating files: 99% (4598/4644)\\nUpdating files: 100% (4644/4644)\\nUpdating files: 100% (4644/4644), done.\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work\", \"duration_seconds\": 0.05488121300004423, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work\", \"duration_seconds\": 0.12047394999535754, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.07353177799086552, \"clone\": 3.744095923990244, \"diff\": 0.0205535150016658, \"edit\": 0.006582002999493852, \"fsck\": 0.2215479599981336, \"read_search\": 0.012863876996561885, \"status\": 0.17539117000706028}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work\", \"duration_seconds\": 0.006875639999634586, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 4.9555087110056775}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.0205535150016658, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work", + "duration_seconds": 0.009685308003099635, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work", + "duration_seconds": 0.005176992999622598, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work", + "duration_seconds": 0.005619338000542484, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.006582002999493852, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work", + "duration_seconds": 0.22152803500648588, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work", + "duration_seconds": 0.07085387800179888, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/mirror.git", + "/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/agentfs-base", + "duration_seconds": 3.7440430769929662, + "returncode": 0, + "stderr_bytes": 2637, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\nUpdating files: 29% (1349/4644)\nUpdating files: 30% (1394/4644)\nUpdating files: 31% (1440/4644)\nUpdating files: 32% (1487/4644)\nUpdating files: 33% (1533/4644)\nUpdating files: 34% (1579/4644)\nUpdating files: 35% (1626/4644)\nUpdating files: 36% (1672/4644)\nUpdating files: 37% (1719/4644)\nUpdating files: 38% (1765/4644)\nUpdating files: 39% (1812/4644)\nUpdating files: 40% (1858/4644)\nUpdating files: 41% (1905/4644)\nUpdating files: 42% (1951/4644)\nUpdating files: 43% (1997/4644)\nUpdating files: 44% (2044/4644)\nUpdating files: 45% (2090/4644)\nUpdating files: 46% (2137/4644)\nUpdating files: 47% (2183/4644)\nUpdating files: 48% (2230/4644)\nUpdating files: 49% (2276/4644)\nUpdating files: 50% (2322/4644)\nUpdating files: 51% (2369/4644)\nUpdating files: 52% (2415/4644)\nUpdating files: 53% (2462/4644)\nUpdating files: 54% (2508/4644)\nUpdating files: 55% (2555/4644)\nUpdating files: 56% (2601/4644)\nUpdating files: 56% (2627/4644)\nUpdating files: 57% (2648/4644)\nUpdating files: 58% (2694/4644)\nUpdating files: 59% (2740/4644)\nUpdating files: 60% (2787/4644)\nUpdating files: 61% (2833/4644)\nUpdating files: 62% (2880/4644)\nUpdating files: 63% (2926/4644)\nUpdating files: 64% (2973/4644)\nUpdating files: 65% (3019/4644)\nUpdating files: 66% (3066/4644)\nUpdating files: 67% (3112/4644)\nUpdating files: 68% (3158/4644)\nUpdating files: 69% (3205/4644)\nUpdating files: 70% (3251/4644)\nUpdating files: 71% (3298/4644)\nUpdating files: 72% (3344/4644)\nUpdating files: 73% (3391/4644)\nUpdating files: 74% (3437/4644)\nUpdating files: 75% (3483/4644)\nUpdating files: 76% (3530/4644)\nUpdating files: 77% (3576/4644)\nUpdating files: 78% (3623/4644)\nUpdating files: 79% (3669/4644)\nUpdating files: 80% (3716/4644)\nUpdating files: 81% (3762/4644)\nUpdating files: 82% (3809/4644)\nUpdating files: 83% (3855/4644)\nUpdating files: 84% (3901/4644)\nUpdating files: 85% (3948/4644)\nUpdating files: 86% (3994/4644)\nUpdating files: 86% (4025/4644)\nUpdating files: 87% (4041/4644)\nUpdating files: 88% (4087/4644)\nUpdating files: 89% (4134/4644)\nUpdating files: 90% (4180/4644)\nUpdating files: 91% (4227/4644)\nUpdating files: 92% (4273/4644)\nUpdating files: 93% (4319/4644)\nUpdating files: 94% (4366/4644)\nUpdating files: 95% (4412/4644)\nUpdating files: 96% (4459/4644)\nUpdating files: 97% (4505/4644)\nUpdating files: 98% (4552/4644)\nUpdating files: 99% (4598/4644)\nUpdating files: 100% (4644/4644)\nUpdating files: 100% (4644/4644), done.\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work", + "duration_seconds": 0.05488121300004423, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work", + "duration_seconds": 0.12047394999535754, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.07353177799086552, + "clone": 3.744095923990244, + "diff": 0.0205535150016658, + "edit": 0.006582002999493852, + "fsck": 0.2215479599981336, + "read_search": 0.012863876996561885, + "status": 0.17539117000706028 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/agentfs-base/work", + "duration_seconds": 0.006875639999634586, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 4.9555087110056775 + } + }, + "base_tree": { + "after": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "6fdb821191321fa3b555a47864b806a160e9ed966f33c59a896495aeb60ec6d3", + "symlinks": 0 + }, + "before": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "6fdb821191321fa3b555a47864b806a160e9ed966f33c59a896495aeb60ec6d3", + "symlinks": 0 + }, + "unchanged": true + }, + "benchmark": "phase7-git-workload", + "command": { + "agentfs_prefix": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-b5988d96b4e44e47b6434b7eadaa47f7", + "--no-default-allows", + "--" + ], + "argv": [ + "/home/ain3sh/factory/vfs/scripts/validation/git-workload-benchmark.py", + "--output", + "/home/ain3sh/factory/vfs/.agents/benchmarks/restructure-baseline/medians.json.iterations/iter-04.json", + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "workload_argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ] + }, + "correctness": { + "agentfs_backup_verify": true, + "agentfs_base_unchanged": true, + "agentfs_db_inspectable": true, + "agentfs_integrity_require_portable": true, + "agentfs_no_nonempty_sidecars": true, + "agentfs_portable": true, + "agentfs_returncode_zero": true, + "equivalence": { + "agentfs": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + }, + "checked": true, + "equivalent": true, + "native": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + } + }, + "native_returncode_zero": true, + "passed": true, + "performance_passed": false + }, + "database": { + "after": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-0w61kvk2/home/.agentfs/run/git-workload-b5988d96b4e44e47b6434b7eadaa47f7/delta.db" + } + ], + "path": "/tmp/agentfs-git-workload-0w61kvk2/home/.agentfs/run/git-workload-b5988d96b4e44e47b6434b7eadaa47f7/delta.db", + "total_bytes": 57147392 + }, + "backup": { + "artifacts": { + "artifacts": [ + { + "bytes": 57147392, + "path": "/tmp/agentfs-git-workload-0w61kvk2/git-workload-backup.db" + } + ], + "path": "/tmp/agentfs-git-workload-0w61kvk2/git-workload-backup.db", + "total_bytes": 57147392 + }, + "inspect": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "path": "/tmp/agentfs-git-workload-0w61kvk2/git-workload-backup.db", + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "backup", + "/tmp/agentfs-git-workload-0w61kvk2/home/.agentfs/run/git-workload-b5988d96b4e44e47b6434b7eadaa47f7/delta.db", + "/tmp/agentfs-git-workload-0w61kvk2/git-workload-backup.db", + "--verify" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2", + "duration_seconds": 2.811011388010229, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 241, + "stdout_tail": "Source: /tmp/agentfs-git-workload-0w61kvk2/home/.agentfs/run/git-workload-b5988d96b4e44e47b6434b7eadaa47f7/delta.db\nBackup: /tmp/agentfs-git-workload-0w61kvk2/git-workload-backup.db\nCheckpoint: complete\nCopy: complete\nVerification: complete\n", + "timed_out": false + } + }, + "inspect_after": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "integrity": { + "result": { + "checks": [ + { + "detail": "ok", + "name": "pragma.integrity_check", + "ok": true, + "violating_rows": null + }, + { + "detail": "present", + "name": "schema.table.fs_config", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_symlink", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.kv_store", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.tool_calls", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 0.5", + "name": "config.schema_version", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 65536", + "name": "config.chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 16384", + "name": "config.inline_threshold", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.kind_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_has_no_chunks", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunked_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_size_matches_blob", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.non_regular_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_reference_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunk_length_within_chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 1, expected 1", + "name": "namespace.root_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_is_directory", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_target_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_root_inode_has_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_names_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_directory_nlink_matches_dentries", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.directory_nlink_positive", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.rows_reference_symlink_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.inodes_have_rows", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable: no partial-origin rows", + "name": "overlay.portability_status", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable requirement satisfied", + "name": "overlay.require_portable", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_regular", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_sizes_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_paths_absolute", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_references_partial_origin", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_unique", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_index_in_range", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.whiteout_paths_absolute", + "ok": true, + "violating_rows": 0 + } + ], + "database": "/tmp/agentfs-git-workload-0w61kvk2/home/.agentfs/run/git-workload-b5988d96b4e44e47b6434b7eadaa47f7/delta.db", + "ok": true, + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true + }, + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "integrity", + "/tmp/agentfs-git-workload-0w61kvk2/home/.agentfs/run/git-workload-b5988d96b4e44e47b6434b7eadaa47f7/delta.db", + "--json", + "--require-portable" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2", + "duration_seconds": 2.842987666997942, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 6395, + "stdout_tail": "{\n \"database\": \"/tmp/agentfs-git-workload-0w61kvk2/home/.agentfs/run/git-workload-b5988d96b4e44e47b6434b7eadaa47f7/delta.db\",\n \"ok\": true,\n \"portable\": true,\n \"origin_backed\": false,\n \"partial_origin_rows\": 0,\n \"checks\": [\n {\n \"name\": \"pragma.integrity_check\",\n \"ok\": true,\n \"detail\": \"ok\",\n \"violating_rows\": null\n },\n {\n \"name\": \"schema.table.fs_config\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_inode\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_dentry\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_data\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_symlink\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.kv_store\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.tool_calls\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.schema_version\",\n \"ok\": true,\n \"detail\": \"found 0.5\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.chunk_size\",\n \"ok\": true,\n \"detail\": \"found 65536\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.inline_threshold\",\n \"ok\": true,\n \"detail\": \"found 16384\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.kind_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_has_no_chunks\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunked_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_size_matches_blob\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.non_regular_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_reference_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunk_length_within_chunk_size\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.root_inode\",\n \"ok\": true,\n \"detail\": \"found 1, expected 1\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_is_directory\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_target_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_root_inode_has_dentry\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_names_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_directory_nlink_matches_dentries\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.directory_nlink_positive\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.rows_reference_symlink_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.inodes_have_rows\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.portability_status\",\n \"ok\": true,\n \"detail\": \"portable: no partial-origin rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.require_portable\",\n \"ok\": true,\n \"detail\": \"portable requirement satisfied\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_regular\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_sizes_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_references_partial_origin\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_unique\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_index_in_range\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.whiteout_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n }\n ]\n}\n", + "timed_out": false + } + }, + "nonempty_sidecars": false + }, + "environment": { + "AGENTFS_BIN": "/home/ain3sh/factory/vfs/target/release/agentfs", + "AGENTFS_PROFILE": "1" + }, + "git_commit": "ae1e13fcb1c52d5c57be051610a04aa9a318f2a5", + "kept_temp": false, + "native": { + "run": { + "argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/native", + "duration_seconds": 1.1284674539929256, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 15982, + "stdout_tail": "{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.21794543499709107, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/native/work\", \"duration_seconds\": 0.08042926600319333, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/native/work\", \"duration_seconds\": 0.065394956007367, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/native/work\", \"duration_seconds\": 0.07207178299722727, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.0005188820068724453, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/native/work\", \"duration_seconds\": 0.2014643669972429, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/native/work\", \"duration_seconds\": 0.1352031429996714, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-0w61kvk2/native/mirror.git\", \"/tmp/agentfs-git-workload-0w61kvk2/native/work\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/native\", \"duration_seconds\": 0.34667103600804694, \"returncode\": 0, \"stderr_bytes\": 149, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-0w61kvk2/native/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/native/work\", \"duration_seconds\": 0.0702247260051081, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/native/work\", \"duration_seconds\": 0.06235981899953913, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.13785997001104988, \"clone\": 0.3467116099927807, \"diff\": 0.21794543499709107, \"edit\": 0.0005188820068724453, \"fsck\": 0.20147872300003655, \"read_search\": 0.005937191002885811, \"status\": 0.13260494700807612}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-0w61kvk2/native/work\", \"duration_seconds\": 0.0035224039893364534, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 1.0432535720028682}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.21794543499709107, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/native/work", + "duration_seconds": 0.08042926600319333, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/native/work", + "duration_seconds": 0.065394956007367, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/native/work", + "duration_seconds": 0.07207178299722727, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.0005188820068724453, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/native/work", + "duration_seconds": 0.2014643669972429, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/native/work", + "duration_seconds": 0.1352031429996714, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-0w61kvk2/native/mirror.git", + "/tmp/agentfs-git-workload-0w61kvk2/native/work" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/native", + "duration_seconds": 0.34667103600804694, + "returncode": 0, + "stderr_bytes": 149, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-0w61kvk2/native/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/native/work", + "duration_seconds": 0.0702247260051081, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/native/work", + "duration_seconds": 0.06235981899953913, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.13785997001104988, + "clone": 0.3467116099927807, + "diff": 0.21794543499709107, + "edit": 0.0005188820068724453, + "fsck": 0.20147872300003655, + "read_search": 0.005937191002885811, + "status": 0.13260494700807612 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-0w61kvk2/native/work", + "duration_seconds": 0.0035224039893364534, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 1.0432535720028682 + } + }, + "parameters": { + "edit_files": 8, + "fixture_dirs": 8, + "fixture_file_size_bytes": 1024, + "fixture_files": 96, + "read_bytes": 4096, + "read_files": 64, + "search_token": "AGENTFS_TOKEN", + "skip_fsck": false, + "timeout_seconds": 180.0 + }, + "schema_version": 1, + "source": { + "kind": "source", + "mirror_head": "7d47056ea42636271ac020b86347fbbef49490aa", + "path": "/home/ain3sh/factory/vfs/.agents/benchmarks/fixtures/codex" + }, + "summary": { + "agentfs_base_unchanged": true, + "agentfs_seconds": 4.9555087110056775, + "all_equivalent": true, + "correctness_passed": true, + "native_seconds": 1.0432535720028682, + "passed": true, + "performance_passed": false, + "phase_ratios": { + "checkout": { + "agentfs_seconds": 0.07353177799086552, + "native_seconds": 0.13785997001104988, + "ratio": 0.5333801972027974 + }, + "clone": { + "agentfs_seconds": 3.744095923990244, + "native_seconds": 0.3467116099927807, + "ratio": 10.798876691980993 + }, + "diff": { + "agentfs_seconds": 0.0205535150016658, + "native_seconds": 0.21794543499709107, + "ratio": 0.0943057834725473 + }, + "edit": { + "agentfs_seconds": 0.006582002999493852, + "native_seconds": 0.0005188820068724453, + "ratio": 12.684970595081513 + }, + "fsck": { + "agentfs_seconds": 0.2215479599981336, + "native_seconds": 0.20147872300003655, + "ratio": 1.0996097091507444 + }, + "read_search": { + "agentfs_seconds": 0.012863876996561885, + "native_seconds": 0.005937191002885811, + "ratio": 2.1666604612028335 + }, + "status": { + "agentfs_seconds": 0.17539117000706028, + "native_seconds": 0.13260494700807612, + "ratio": 1.3226593273053253 + } + }, + "ratio": 4.750051995021641, + "threshold_failures": [ + { + "agentfs_seconds": 3.744095923990244, + "native_seconds": 0.3467116099927807, + "phase": "clone", + "ratio": 10.798876691980993 + }, + { + "agentfs_seconds": 0.006582002999493852, + "native_seconds": 0.0005188820068724453, + "phase": "edit", + "ratio": 12.684970595081513 + }, + { + "agentfs_seconds": 0.012863876996561885, + "native_seconds": 0.005937191002885811, + "phase": "read_search", + "ratio": 2.1666604612028335 + } + ] + }, + "temp_dir": "/tmp/agentfs-git-workload-0w61kvk2" +} diff --git a/.agents/benchmarks/restructure-baseline/medians.json.iterations/warmup-00.json b/.agents/benchmarks/restructure-baseline/medians.json.iterations/warmup-00.json new file mode 100644 index 00000000..8ae7c7e2 --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/medians.json.iterations/warmup-00.json @@ -0,0 +1,6630 @@ +{ + "agentfs": { + "bin": "/home/ain3sh/factory/vfs/target/release/agentfs", + "db_path": "/tmp/agentfs-git-workload-9hklcrln/home/.agentfs/run/git-workload-e13728a82daa4c4a90c2edf69ec5b3f4/delta.db", + "per_phase_counters": { + "checkpoint_count": 7, + "label_count": 7, + "labels_aligned": true, + "phases": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 936722602, + "agentfs_batcher_commit_txns": 4792, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4732, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25920, + "attr_cache_misses": 29058, + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create_count": 2, + "connection_reuse_count": 31218, + "connection_wait_count": 31220, + "connection_wait_nanos": 12113271, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 89, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5335, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21833, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 266801, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4732, + "fuse_flush_ranges": 4732, + "fuse_getattr_count": 5023, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11752, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 660874071, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 35, + "fuse_op_forget_nanos": 333374, + "fuse_op_getattr_count": 5023, + "fuse_op_getattr_nanos": 61383450, + "fuse_op_lookup_count": 11752, + "fuse_op_lookup_nanos": 155973636, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 785, + "fuse_op_other_nanos": 140982740, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27814717, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1501529, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1324298124, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 31025840, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16155, + "fuse_read_lane_wait_nanos": 1765820, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32057, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673690, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1681382, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19338, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6829, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "phase": "clone", + "seq": 1 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 2, + "agentfs_batcher_commit_latency_ns_total": 3894289, + "agentfs_batcher_commit_txns": 9, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 5, + "agentfs_batcher_drains_timer": 2, + "agentfs_batcher_enqueues": 9, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 14, + "attr_cache_hits": 196, + "attr_cache_misses": 1727, + "base_fast_inode_invalidations": 51, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 12, + "connection_create_count": 8, + "connection_reuse_count": 621, + "connection_wait_count": 629, + "connection_wait_nanos": 274424, + "dentry_cache_hits": 73, + "dentry_cache_misses": 56, + "fuse_adapter_attr_hits": 4620, + "fuse_adapter_attr_misses": 649, + "fuse_adapter_entry_hits": 2, + "fuse_adapter_entry_misses": 35, + "fuse_adapter_inval_entry_notifications": 10, + "fuse_adapter_inval_inode_notifications": 28, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 22, + "fuse_adapter_negative_misses": 35, + "fuse_callback_count": 5349, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 5, + "fuse_dispatch_parallel_tasks": 9, + "fuse_dispatch_wait_count": 9, + "fuse_dispatch_wait_nanos": 61316, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 694731, + "fuse_flush_count": 9, + "fuse_flush_ranges": 9, + "fuse_getattr_count": 5269, + "fuse_ino_file_resolutions": 3, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 59, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 9, + "fuse_op_create_nanos": 1621614, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 9, + "fuse_op_forget_nanos": 58290, + "fuse_op_getattr_count": 5269, + "fuse_op_getattr_nanos": 38476409, + "fuse_op_lookup_count": 59, + "fuse_op_lookup_nanos": 866372, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 15, + "fuse_op_other_nanos": 4427275, + "fuse_op_read_count": 8, + "fuse_op_read_nanos": 1018117, + "fuse_op_readdirplus_count": 4, + "fuse_op_readdirplus_nanos": 137010, + "fuse_op_setattr_count": 14, + "fuse_op_setattr_nanos": 3853759, + "fuse_op_write_count": 9, + "fuse_op_write_nanos": 192075, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 8, + "fuse_read_lane_max_concurrent": 5, + "fuse_read_lane_wait_count": 714, + "fuse_read_lane_wait_nanos": 99519, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 4, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 5387, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 591393, + "fuse_write_count": 9, + "fuse_write_lane_wait_count": 42, + "fuse_write_lane_wait_nanos": 34199, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 1326, + "lookup_base_count": 0, + "lookup_count": 164, + "lookup_delta_count": 60, + "lookup_whiteout_count": 0, + "negative_cache_hits": 43, + "negative_cache_invalidations": 26, + "negative_cache_misses": 148, + "negative_lookup_count": 86, + "path_cache_hits": 73, + "path_cache_misses": 56, + "path_component_count": 124, + "path_resolution_count": 45, + "readdir_count": 0, + "readdir_plus_count": 2, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "checkout", + "seq": 2 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 644901, + "agentfs_batcher_commit_txns": 2, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 2, + "attr_cache_hits": 14, + "attr_cache_misses": 21, + "base_fast_inode_invalidations": 8, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 10, + "chunk_read_queries": 6, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 999, + "connection_wait_count": 999, + "connection_wait_nanos": 386936, + "dentry_cache_hits": 13, + "dentry_cache_misses": 570, + "fuse_adapter_attr_hits": 2321, + "fuse_adapter_attr_misses": 7, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 288, + "fuse_adapter_inval_entry_notifications": 2, + "fuse_adapter_inval_inode_notifications": 6, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 288, + "fuse_callback_count": 4003, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 2, + "fuse_dispatch_wait_count": 2, + "fuse_dispatch_wait_nanos": 100881, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 2328, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 288, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 2, + "fuse_op_create_nanos": 1288916, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 2, + "fuse_op_forget_nanos": 36968, + "fuse_op_getattr_count": 2328, + "fuse_op_getattr_nanos": 2913323, + "fuse_op_lookup_count": 288, + "fuse_op_lookup_nanos": 5365656, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 2762, + "fuse_op_other_nanos": 2593189, + "fuse_op_read_count": 6, + "fuse_op_read_nanos": 1247062, + "fuse_op_readdirplus_count": 1381, + "fuse_op_readdirplus_nanos": 57519180, + "fuse_op_setattr_count": 2, + "fuse_op_setattr_nanos": 949408, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 6, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 988, + "fuse_read_lane_wait_nanos": 122009, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1381, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 6769, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 3866, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 18, + "lookup_base_count": 0, + "lookup_count": 594, + "lookup_delta_count": 294, + "lookup_whiteout_count": 0, + "negative_cache_hits": 4, + "negative_cache_invalidations": 4, + "negative_cache_misses": 869, + "negative_lookup_count": 578, + "path_cache_hits": 13, + "path_cache_misses": 570, + "path_component_count": 4606, + "path_resolution_count": 977, + "readdir_count": 0, + "readdir_plus_count": 688, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "status", + "seq": 3 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 69, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 69, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 69, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 69, + "fuse_op_getattr_nanos": 136225, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 0, + "fuse_op_other_nanos": 0, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 69, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "read_search", + "seq": 4 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 1263057, + "agentfs_batcher_commit_txns": 8, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 8, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 8, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 8, + "attr_cache_hits": 0, + "attr_cache_misses": 24, + "base_fast_inode_invalidations": 16, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 32, + "connection_wait_count": 32, + "connection_wait_nanos": 22353, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 8, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 0, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 16, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 6398, + "fuse_flush_count": 8, + "fuse_flush_ranges": 8, + "fuse_getattr_count": 8, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 0, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 8, + "fuse_op_getattr_nanos": 10076, + "fuse_op_lookup_count": 0, + "fuse_op_lookup_nanos": 0, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 8, + "fuse_op_other_nanos": 846735, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 8, + "fuse_op_setattr_nanos": 2194904, + "fuse_op_write_count": 8, + "fuse_op_write_nanos": 664499, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 8, + "fuse_read_lane_wait_nanos": 1167, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 32, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 6398, + "fuse_write_count": 8, + "fuse_write_lane_wait_count": 8, + "fuse_write_lane_wait_nanos": 1634, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 16, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 0, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 8, + "wal_checkpoint_nanos": 718662 + }, + "phase": "edit", + "seq": 5 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 0, + "attr_cache_misses": 0, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 0, + "chunk_read_queries": 0, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 0, + "connection_wait_count": 0, + "connection_wait_nanos": 0, + "dentry_cache_hits": 0, + "dentry_cache_misses": 0, + "fuse_adapter_attr_hits": 57, + "fuse_adapter_attr_misses": 0, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 0, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 11, + "fuse_adapter_negative_misses": 0, + "fuse_callback_count": 68, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 57, + "fuse_ino_file_resolutions": 0, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 11, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 57, + "fuse_op_getattr_nanos": 98047, + "fuse_op_lookup_count": 11, + "fuse_op_lookup_nanos": 15208, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 12, + "fuse_op_other_nanos": 9273, + "fuse_op_read_count": 0, + "fuse_op_read_nanos": 0, + "fuse_op_readdirplus_count": 0, + "fuse_op_readdirplus_nanos": 0, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 0, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 0, + "fuse_read_lane_wait_nanos": 0, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 0, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 80, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 0, + "lookup_base_count": 0, + "lookup_count": 0, + "lookup_delta_count": 0, + "lookup_whiteout_count": 0, + "negative_cache_hits": 11, + "negative_cache_invalidations": 0, + "negative_cache_misses": 0, + "negative_lookup_count": 0, + "path_cache_hits": 0, + "path_cache_misses": 0, + "path_component_count": 0, + "path_resolution_count": 0, + "readdir_count": 0, + "readdir_plus_count": 0, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "diff", + "seq": 6 + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 0, + "agentfs_batcher_commit_latency_ns_total": 0, + "agentfs_batcher_commit_txns": 0, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 0, + "agentfs_batcher_drains_timer": 0, + "agentfs_batcher_enqueues": 0, + "agentfs_batcher_pending_max_bytes": 0, + "agentfs_batcher_txn_inodes_max": 0, + "agentfs_batcher_txn_inodes_total": 0, + "attr_cache_hits": 3, + "attr_cache_misses": 2, + "base_fast_inode_invalidations": 0, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 1, + "chunk_read_queries": 1, + "chunk_write_chunks": 0, + "connection_create_count": 0, + "connection_reuse_count": 15, + "connection_wait_count": 15, + "connection_wait_nanos": 22809, + "dentry_cache_hits": 4, + "dentry_cache_misses": 2, + "fuse_adapter_attr_hits": 44, + "fuse_adapter_attr_misses": 1, + "fuse_adapter_entry_hits": 0, + "fuse_adapter_entry_misses": 4, + "fuse_adapter_inval_entry_notifications": 0, + "fuse_adapter_inval_inode_notifications": 0, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 215, + "fuse_adapter_negative_misses": 4, + "fuse_callback_count": 284, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 0, + "fuse_dispatch_parallel_tasks": 0, + "fuse_dispatch_wait_count": 0, + "fuse_dispatch_wait_nanos": 0, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 0, + "fuse_flush_count": 0, + "fuse_flush_ranges": 0, + "fuse_getattr_count": 45, + "fuse_ino_file_resolutions": 1, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 0, + "fuse_lookup_count": 219, + "fuse_noflush_enosys_replies": 0, + "fuse_noopen_enosys_replies": 0, + "fuse_op_create_count": 0, + "fuse_op_create_nanos": 0, + "fuse_op_flush_count": 0, + "fuse_op_flush_nanos": 0, + "fuse_op_forget_count": 0, + "fuse_op_forget_nanos": 0, + "fuse_op_getattr_count": 45, + "fuse_op_getattr_nanos": 104377, + "fuse_op_lookup_count": 219, + "fuse_op_lookup_nanos": 311933, + "fuse_op_open_count": 0, + "fuse_op_open_nanos": 0, + "fuse_op_other_count": 36, + "fuse_op_other_nanos": 17082, + "fuse_op_read_count": 2, + "fuse_op_read_nanos": 706163, + "fuse_op_readdirplus_count": 18, + "fuse_op_readdirplus_nanos": 893609, + "fuse_op_setattr_count": 0, + "fuse_op_setattr_nanos": 0, + "fuse_op_write_count": 0, + "fuse_op_write_nanos": 0, + "fuse_open_count": 0, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 2, + "fuse_read_lane_max_concurrent": 0, + "fuse_read_lane_wait_count": 15, + "fuse_read_lane_wait_nanos": 4748, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 18, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 0, + "fuse_readdirplus_do_requested": 0, + "fuse_readdirplus_mode": 0, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 0, + "fuse_ttl_entry_ms": 0, + "fuse_ttl_neg_ms": 0, + "fuse_uring_requests": 320, + "fuse_worker_queue_depth_peak": 0, + "fuse_workers_configured": 0, + "fuse_write_bytes": 0, + "fuse_write_count": 0, + "fuse_write_lane_wait_count": 0, + "fuse_write_lane_wait_nanos": 0, + "fuse_writeback_cache_enabled": 0, + "getattr_count": 2, + "lookup_base_count": 0, + "lookup_count": 8, + "lookup_delta_count": 4, + "lookup_whiteout_count": 0, + "negative_cache_hits": 215, + "negative_cache_invalidations": 0, + "negative_cache_misses": 9, + "negative_lookup_count": 2, + "path_cache_hits": 4, + "path_cache_misses": 2, + "path_component_count": 42, + "path_resolution_count": 10, + "readdir_count": 0, + "readdir_plus_count": 9, + "wal_checkpoint_count": 0, + "wal_checkpoint_nanos": 0 + }, + "phase": "fsck", + "seq": 7 + } + ] + }, + "profile_counters": { + "last_by_source": { + "agentfs": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32888, + "connection_wait_count": 32898, + "connection_wait_nanos": 12825640, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10264, + "fuse_write_lane_wait_nanos": 1721880, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 1346034 + }, + "fuse_session": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32888, + "connection_wait_count": 32898, + "connection_wait_nanos": 12825640, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10264, + "fuse_write_lane_wait_nanos": 1721880, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 1346034 + }, + "phase-checkpoint-1": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 936722602, + "agentfs_batcher_commit_txns": 4792, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4732, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25920, + "attr_cache_misses": 29058, + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create_count": 2, + "connection_reuse_count": 31218, + "connection_wait_count": 31220, + "connection_wait_nanos": 12113271, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 89, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5335, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21833, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 266801, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4732, + "fuse_flush_ranges": 4732, + "fuse_getattr_count": 5023, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11752, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 660874071, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 35, + "fuse_op_forget_nanos": 333374, + "fuse_op_getattr_count": 5023, + "fuse_op_getattr_nanos": 61383450, + "fuse_op_lookup_count": 11752, + "fuse_op_lookup_nanos": 155973636, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 785, + "fuse_op_other_nanos": 140982740, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27814717, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1501529, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1324298124, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 31025840, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16155, + "fuse_read_lane_wait_nanos": 1765820, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32057, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673690, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1681382, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19338, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6829, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "phase-checkpoint-2": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 940616891, + "agentfs_batcher_commit_txns": 4801, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4741, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26116, + "attr_cache_misses": 30785, + "base_fast_inode_invalidations": 15082, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 31839, + "connection_wait_count": 31849, + "connection_wait_nanos": 12387695, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4709, + "fuse_adapter_attr_misses": 5583, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27182, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 328117, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4741, + "fuse_flush_ranges": 4741, + "fuse_getattr_count": 10292, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11811, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 662495685, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 391664, + "fuse_op_getattr_count": 10292, + "fuse_op_getattr_nanos": 99859859, + "fuse_op_lookup_count": 11811, + "fuse_op_lookup_nanos": 156840008, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 800, + "fuse_op_other_nanos": 145410015, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 28832834, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1638539, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1328151883, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 31217915, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 16869, + "fuse_read_lane_wait_nanos": 1865339, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37444, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265083, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1715581, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20664, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6872, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "phase-checkpoint-3": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 941261792, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4741, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9401, + "attr_cache_hits": 26130, + "attr_cache_misses": 30806, + "base_fast_inode_invalidations": 15090, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 247, + "chunk_read_queries": 110, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32838, + "connection_wait_count": 32848, + "connection_wait_nanos": 12774631, + "dentry_cache_hits": 26119, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7030, + "fuse_adapter_attr_misses": 5590, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6732, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6732, + "fuse_callback_count": 31185, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4741, + "fuse_flush_ranges": 4741, + "fuse_getattr_count": 12620, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12099, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12620, + "fuse_op_getattr_nanos": 102773182, + "fuse_op_lookup_count": 12099, + "fuse_op_lookup_nanos": 162205664, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3562, + "fuse_op_other_nanos": 148003204, + "fuse_op_read_count": 227, + "fuse_op_read_nanos": 30079896, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 59157719, + "fuse_op_setattr_count": 4765, + "fuse_op_setattr_nanos": 1329101291, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 31217915, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 227, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17857, + "fuse_read_lane_wait_nanos": 1987348, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44213, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265083, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10254, + "fuse_write_lane_wait_nanos": 1719447, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20682, + "lookup_base_count": 23, + "lookup_count": 41046, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6876, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46079, + "negative_lookup_count": 14920, + "path_cache_hits": 26119, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "phase-checkpoint-4": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 941261792, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4741, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9401, + "attr_cache_hits": 26130, + "attr_cache_misses": 30806, + "base_fast_inode_invalidations": 15090, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 247, + "chunk_read_queries": 110, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32838, + "connection_wait_count": 32848, + "connection_wait_nanos": 12774631, + "dentry_cache_hits": 26119, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7099, + "fuse_adapter_attr_misses": 5590, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6732, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6732, + "fuse_callback_count": 31254, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4741, + "fuse_flush_ranges": 4741, + "fuse_getattr_count": 12689, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12099, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12689, + "fuse_op_getattr_nanos": 102909407, + "fuse_op_lookup_count": 12099, + "fuse_op_lookup_nanos": 162205664, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3562, + "fuse_op_other_nanos": 148003204, + "fuse_op_read_count": 227, + "fuse_op_read_nanos": 30079896, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 59157719, + "fuse_op_setattr_count": 4765, + "fuse_op_setattr_nanos": 1329101291, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 31217915, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 227, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17857, + "fuse_read_lane_wait_nanos": 1987348, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44282, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265083, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10254, + "fuse_write_lane_wait_nanos": 1719447, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20682, + "lookup_base_count": 23, + "lookup_count": 41046, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6876, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46079, + "negative_lookup_count": 14920, + "path_cache_hits": 26119, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "phase-checkpoint-5": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26130, + "attr_cache_misses": 30830, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 247, + "chunk_read_queries": 110, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32870, + "connection_wait_count": 32880, + "connection_wait_nanos": 12796984, + "dentry_cache_hits": 26119, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7107, + "fuse_adapter_attr_misses": 5590, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6732, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6732, + "fuse_callback_count": 31270, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12697, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12099, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12697, + "fuse_op_getattr_nanos": 102919483, + "fuse_op_lookup_count": 12099, + "fuse_op_lookup_nanos": 162205664, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3570, + "fuse_op_other_nanos": 148849939, + "fuse_op_read_count": 227, + "fuse_op_read_nanos": 30079896, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 59157719, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 227, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17865, + "fuse_read_lane_wait_nanos": 1988515, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44314, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10262, + "fuse_write_lane_wait_nanos": 1721081, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41046, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6876, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46079, + "negative_lookup_count": 14920, + "path_cache_hits": 26119, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 1214311 + }, + "phase-checkpoint-6": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26130, + "attr_cache_misses": 30830, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 247, + "chunk_read_queries": 110, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32870, + "connection_wait_count": 32880, + "connection_wait_nanos": 12796984, + "dentry_cache_hits": 26119, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7164, + "fuse_adapter_attr_misses": 5590, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6732, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5368, + "fuse_adapter_negative_misses": 6732, + "fuse_callback_count": 31338, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12754, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12110, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12754, + "fuse_op_getattr_nanos": 103017530, + "fuse_op_lookup_count": 12110, + "fuse_op_lookup_nanos": 162220872, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3582, + "fuse_op_other_nanos": 148859212, + "fuse_op_read_count": 227, + "fuse_op_read_nanos": 30079896, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 59157719, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 227, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17865, + "fuse_read_lane_wait_nanos": 1988515, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44394, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10262, + "fuse_write_lane_wait_nanos": 1721081, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41046, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6887, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46079, + "negative_lookup_count": 14920, + "path_cache_hits": 26119, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 1214311 + }, + "phase-checkpoint-7": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32885, + "connection_wait_count": 32895, + "connection_wait_nanos": 12819793, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10262, + "fuse_write_lane_wait_nanos": 1721081, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 1214311 + }, + "run_parent": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32888, + "connection_wait_count": 32898, + "connection_wait_nanos": 12825640, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10264, + "fuse_write_lane_wait_nanos": 1721880, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 1346034 + } + }, + "max_counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32888, + "connection_wait_count": 32898, + "connection_wait_nanos": 12825640, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10264, + "fuse_write_lane_wait_nanos": 1721880, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 1346034 + }, + "summary_count": 10 + }, + "profile_enabled": true, + "profile_summary_count": 10, + "session": "git-workload-e13728a82daa4c4a90c2edf69ec5b3f4" + }, + "agentfs_overlay": { + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 936722602, + "agentfs_batcher_commit_txns": 4792, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4732, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25920, + "attr_cache_misses": 29058, + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create_count": 2, + "connection_reuse_count": 31218, + "connection_wait_count": 31220, + "connection_wait_nanos": 12113271, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 89, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5335, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21833, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 266801, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4732, + "fuse_flush_ranges": 4732, + "fuse_getattr_count": 5023, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11752, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 660874071, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 35, + "fuse_op_forget_nanos": 333374, + "fuse_op_getattr_count": 5023, + "fuse_op_getattr_nanos": 61383450, + "fuse_op_lookup_count": 11752, + "fuse_op_lookup_nanos": 155973636, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 785, + "fuse_op_other_nanos": 140982740, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27814717, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1501529, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1324298124, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 31025840, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16155, + "fuse_read_lane_wait_nanos": 1765820, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32057, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673690, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1681382, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19338, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6829, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 940616891, + "agentfs_batcher_commit_txns": 4801, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4741, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26116, + "attr_cache_misses": 30785, + "base_fast_inode_invalidations": 15082, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 31839, + "connection_wait_count": 31849, + "connection_wait_nanos": 12387695, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4709, + "fuse_adapter_attr_misses": 5583, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27182, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 328117, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4741, + "fuse_flush_ranges": 4741, + "fuse_getattr_count": 10292, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11811, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 662495685, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 391664, + "fuse_op_getattr_count": 10292, + "fuse_op_getattr_nanos": 99859859, + "fuse_op_lookup_count": 11811, + "fuse_op_lookup_nanos": 156840008, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 800, + "fuse_op_other_nanos": 145410015, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 28832834, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1638539, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1328151883, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 31217915, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 16869, + "fuse_read_lane_wait_nanos": 1865339, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37444, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265083, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1715581, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20664, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6872, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 941261792, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4741, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9401, + "attr_cache_hits": 26130, + "attr_cache_misses": 30806, + "base_fast_inode_invalidations": 15090, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 247, + "chunk_read_queries": 110, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32838, + "connection_wait_count": 32848, + "connection_wait_nanos": 12774631, + "dentry_cache_hits": 26119, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7030, + "fuse_adapter_attr_misses": 5590, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6732, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6732, + "fuse_callback_count": 31185, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4741, + "fuse_flush_ranges": 4741, + "fuse_getattr_count": 12620, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12099, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12620, + "fuse_op_getattr_nanos": 102773182, + "fuse_op_lookup_count": 12099, + "fuse_op_lookup_nanos": 162205664, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3562, + "fuse_op_other_nanos": 148003204, + "fuse_op_read_count": 227, + "fuse_op_read_nanos": 30079896, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 59157719, + "fuse_op_setattr_count": 4765, + "fuse_op_setattr_nanos": 1329101291, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 31217915, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 227, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17857, + "fuse_read_lane_wait_nanos": 1987348, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44213, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265083, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10254, + "fuse_write_lane_wait_nanos": 1719447, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20682, + "lookup_base_count": 23, + "lookup_count": 41046, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6876, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46079, + "negative_lookup_count": 14920, + "path_cache_hits": 26119, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 941261792, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4741, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9401, + "attr_cache_hits": 26130, + "attr_cache_misses": 30806, + "base_fast_inode_invalidations": 15090, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 247, + "chunk_read_queries": 110, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32838, + "connection_wait_count": 32848, + "connection_wait_nanos": 12774631, + "dentry_cache_hits": 26119, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7099, + "fuse_adapter_attr_misses": 5590, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6732, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6732, + "fuse_callback_count": 31254, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4741, + "fuse_flush_ranges": 4741, + "fuse_getattr_count": 12689, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12099, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12689, + "fuse_op_getattr_nanos": 102909407, + "fuse_op_lookup_count": 12099, + "fuse_op_lookup_nanos": 162205664, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3562, + "fuse_op_other_nanos": 148003204, + "fuse_op_read_count": 227, + "fuse_op_read_nanos": 30079896, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 59157719, + "fuse_op_setattr_count": 4765, + "fuse_op_setattr_nanos": 1329101291, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 31217915, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 227, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17857, + "fuse_read_lane_wait_nanos": 1987348, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44282, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265083, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10254, + "fuse_write_lane_wait_nanos": 1719447, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20682, + "lookup_base_count": 23, + "lookup_count": 41046, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6876, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46079, + "negative_lookup_count": 14920, + "path_cache_hits": 26119, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26130, + "attr_cache_misses": 30830, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 247, + "chunk_read_queries": 110, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32870, + "connection_wait_count": 32880, + "connection_wait_nanos": 12796984, + "dentry_cache_hits": 26119, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7107, + "fuse_adapter_attr_misses": 5590, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6732, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6732, + "fuse_callback_count": 31270, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12697, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12099, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12697, + "fuse_op_getattr_nanos": 102919483, + "fuse_op_lookup_count": 12099, + "fuse_op_lookup_nanos": 162205664, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3570, + "fuse_op_other_nanos": 148849939, + "fuse_op_read_count": 227, + "fuse_op_read_nanos": 30079896, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 59157719, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 227, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17865, + "fuse_read_lane_wait_nanos": 1988515, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44314, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10262, + "fuse_write_lane_wait_nanos": 1721081, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41046, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6876, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46079, + "negative_lookup_count": 14920, + "path_cache_hits": 26119, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 1214311 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26130, + "attr_cache_misses": 30830, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 247, + "chunk_read_queries": 110, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32870, + "connection_wait_count": 32880, + "connection_wait_nanos": 12796984, + "dentry_cache_hits": 26119, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7164, + "fuse_adapter_attr_misses": 5590, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6732, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5368, + "fuse_adapter_negative_misses": 6732, + "fuse_callback_count": 31338, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12754, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12110, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12754, + "fuse_op_getattr_nanos": 103017530, + "fuse_op_lookup_count": 12110, + "fuse_op_lookup_nanos": 162220872, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3582, + "fuse_op_other_nanos": 148859212, + "fuse_op_read_count": 227, + "fuse_op_read_nanos": 30079896, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 59157719, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 227, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17865, + "fuse_read_lane_wait_nanos": 1988515, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44394, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10262, + "fuse_write_lane_wait_nanos": 1721081, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41046, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6887, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46079, + "negative_lookup_count": 14920, + "path_cache_hits": 26119, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 1214311 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32885, + "connection_wait_count": 32895, + "connection_wait_nanos": 12819793, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10262, + "fuse_write_lane_wait_nanos": 1721081, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 1214311 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32888, + "connection_wait_count": 32898, + "connection_wait_nanos": 12825640, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10264, + "fuse_write_lane_wait_nanos": 1721880, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 1346034 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "agentfs" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32888, + "connection_wait_count": 32898, + "connection_wait_nanos": 12825640, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10264, + "fuse_write_lane_wait_nanos": 1721880, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 1346034 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "fuse_session" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32888, + "connection_wait_count": 32898, + "connection_wait_nanos": 12825640, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10264, + "fuse_write_lane_wait_nanos": 1721880, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 1346034 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "run_parent" + } + ], + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-e13728a82daa4c4a90c2edf69ec5b3f4", + "--no-default-allows", + "--", + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/agentfs-base", + "duration_seconds": 4.7149352690030355, + "profile_summaries": [ + { + "counters": { + "agentfs_batcher_coalesced_ranges": 37, + "agentfs_batcher_commit_latency_ns_total": 936722602, + "agentfs_batcher_commit_txns": 4792, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4682, + "agentfs_batcher_drains_timer": 13, + "agentfs_batcher_enqueues": 4732, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9385, + "attr_cache_hits": 25920, + "attr_cache_misses": 29058, + "base_fast_inode_invalidations": 15031, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 227, + "chunk_read_queries": 98, + "chunk_write_chunks": 970, + "connection_create_count": 2, + "connection_reuse_count": 31218, + "connection_wait_count": 31220, + "connection_wait_nanos": 12113271, + "dentry_cache_hits": 26033, + "dentry_cache_misses": 13426, + "fuse_adapter_attr_hits": 89, + "fuse_adapter_attr_misses": 4934, + "fuse_adapter_entry_hits": 8, + "fuse_adapter_entry_misses": 6409, + "fuse_adapter_inval_entry_notifications": 46, + "fuse_adapter_inval_inode_notifications": 5472, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5335, + "fuse_adapter_negative_misses": 6409, + "fuse_callback_count": 21833, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 2, + "fuse_dispatch_parallel_tasks": 39, + "fuse_dispatch_wait_count": 39, + "fuse_dispatch_wait_nanos": 266801, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 52539545, + "fuse_flush_count": 4732, + "fuse_flush_ranges": 4732, + "fuse_getattr_count": 5023, + "fuse_ino_file_resolutions": 21, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11752, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4692, + "fuse_op_create_nanos": 660874071, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 35, + "fuse_op_forget_nanos": 333374, + "fuse_op_getattr_count": 5023, + "fuse_op_getattr_nanos": 61383450, + "fuse_op_lookup_count": 11752, + "fuse_op_lookup_nanos": 155973636, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 785, + "fuse_op_other_nanos": 140982740, + "fuse_op_read_count": 213, + "fuse_op_read_nanos": 27814717, + "fuse_op_readdirplus_count": 22, + "fuse_op_readdirplus_nanos": 1501529, + "fuse_op_setattr_count": 4749, + "fuse_op_setattr_nanos": 1324298124, + "fuse_op_write_count": 4822, + "fuse_op_write_nanos": 31025840, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 213, + "fuse_read_lane_max_concurrent": 1, + "fuse_read_lane_wait_count": 16155, + "fuse_read_lane_wait_nanos": 1765820, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 22, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 32057, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 52673690, + "fuse_write_count": 4822, + "fuse_write_lane_wait_count": 10204, + "fuse_write_lane_wait_nanos": 1681382, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 19338, + "lookup_base_count": 23, + "lookup_count": 40288, + "lookup_delta_count": 7191, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6829, + "negative_cache_invalidations": 10816, + "negative_cache_misses": 45062, + "negative_lookup_count": 14256, + "path_cache_hits": 26033, + "path_cache_misses": 13426, + "path_component_count": 32902, + "path_resolution_count": 7118, + "readdir_count": 1, + "readdir_plus_count": 11, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-1" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 940616891, + "agentfs_batcher_commit_txns": 4801, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4741, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9399, + "attr_cache_hits": 26116, + "attr_cache_misses": 30785, + "base_fast_inode_invalidations": 15082, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 237, + "chunk_read_queries": 104, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 31839, + "connection_wait_count": 31849, + "connection_wait_nanos": 12387695, + "dentry_cache_hits": 26106, + "dentry_cache_misses": 13482, + "fuse_adapter_attr_hits": 4709, + "fuse_adapter_attr_misses": 5583, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6444, + "fuse_adapter_inval_entry_notifications": 56, + "fuse_adapter_inval_inode_notifications": 5500, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6444, + "fuse_callback_count": 27182, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 48, + "fuse_dispatch_wait_count": 48, + "fuse_dispatch_wait_nanos": 328117, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4741, + "fuse_flush_ranges": 4741, + "fuse_getattr_count": 10292, + "fuse_ino_file_resolutions": 24, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 11811, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4701, + "fuse_op_create_nanos": 662495685, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 44, + "fuse_op_forget_nanos": 391664, + "fuse_op_getattr_count": 10292, + "fuse_op_getattr_nanos": 99859859, + "fuse_op_lookup_count": 11811, + "fuse_op_lookup_nanos": 156840008, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 800, + "fuse_op_other_nanos": 145410015, + "fuse_op_read_count": 221, + "fuse_op_read_nanos": 28832834, + "fuse_op_readdirplus_count": 26, + "fuse_op_readdirplus_nanos": 1638539, + "fuse_op_setattr_count": 4763, + "fuse_op_setattr_nanos": 1328151883, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 31217915, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 221, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 16869, + "fuse_read_lane_wait_nanos": 1865339, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 26, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 37444, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265083, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10246, + "fuse_write_lane_wait_nanos": 1715581, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20664, + "lookup_base_count": 23, + "lookup_count": 40452, + "lookup_delta_count": 7251, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6872, + "negative_cache_invalidations": 10842, + "negative_cache_misses": 45210, + "negative_lookup_count": 14342, + "path_cache_hits": 26106, + "path_cache_misses": 13482, + "path_component_count": 33026, + "path_resolution_count": 7163, + "readdir_count": 1, + "readdir_plus_count": 13, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-2" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 941261792, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4741, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9401, + "attr_cache_hits": 26130, + "attr_cache_misses": 30806, + "base_fast_inode_invalidations": 15090, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 247, + "chunk_read_queries": 110, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32838, + "connection_wait_count": 32848, + "connection_wait_nanos": 12774631, + "dentry_cache_hits": 26119, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7030, + "fuse_adapter_attr_misses": 5590, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6732, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6732, + "fuse_callback_count": 31185, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4741, + "fuse_flush_ranges": 4741, + "fuse_getattr_count": 12620, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12099, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12620, + "fuse_op_getattr_nanos": 102773182, + "fuse_op_lookup_count": 12099, + "fuse_op_lookup_nanos": 162205664, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3562, + "fuse_op_other_nanos": 148003204, + "fuse_op_read_count": 227, + "fuse_op_read_nanos": 30079896, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 59157719, + "fuse_op_setattr_count": 4765, + "fuse_op_setattr_nanos": 1329101291, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 31217915, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 227, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17857, + "fuse_read_lane_wait_nanos": 1987348, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44213, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265083, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10254, + "fuse_write_lane_wait_nanos": 1719447, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20682, + "lookup_base_count": 23, + "lookup_count": 41046, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6876, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46079, + "negative_lookup_count": 14920, + "path_cache_hits": 26119, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-3" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 941261792, + "agentfs_batcher_commit_txns": 4803, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4687, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4741, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9401, + "attr_cache_hits": 26130, + "attr_cache_misses": 30806, + "base_fast_inode_invalidations": 15090, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 247, + "chunk_read_queries": 110, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32838, + "connection_wait_count": 32848, + "connection_wait_nanos": 12774631, + "dentry_cache_hits": 26119, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7099, + "fuse_adapter_attr_misses": 5590, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6732, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6732, + "fuse_callback_count": 31254, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53234276, + "fuse_flush_count": 4741, + "fuse_flush_ranges": 4741, + "fuse_getattr_count": 12689, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12099, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12689, + "fuse_op_getattr_nanos": 102909407, + "fuse_op_lookup_count": 12099, + "fuse_op_lookup_nanos": 162205664, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3562, + "fuse_op_other_nanos": 148003204, + "fuse_op_read_count": 227, + "fuse_op_read_nanos": 30079896, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 59157719, + "fuse_op_setattr_count": 4765, + "fuse_op_setattr_nanos": 1329101291, + "fuse_op_write_count": 4831, + "fuse_op_write_nanos": 31217915, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 227, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17857, + "fuse_read_lane_wait_nanos": 1987348, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44282, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53265083, + "fuse_write_count": 4831, + "fuse_write_lane_wait_count": 10254, + "fuse_write_lane_wait_nanos": 1719447, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20682, + "lookup_base_count": 23, + "lookup_count": 41046, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6876, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46079, + "negative_lookup_count": 14920, + "path_cache_hits": 26119, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 3, + "wal_checkpoint_nanos": 495649 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-4" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26130, + "attr_cache_misses": 30830, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 247, + "chunk_read_queries": 110, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32870, + "connection_wait_count": 32880, + "connection_wait_nanos": 12796984, + "dentry_cache_hits": 26119, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7107, + "fuse_adapter_attr_misses": 5590, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6732, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5357, + "fuse_adapter_negative_misses": 6732, + "fuse_callback_count": 31270, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12697, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12099, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12697, + "fuse_op_getattr_nanos": 102919483, + "fuse_op_lookup_count": 12099, + "fuse_op_lookup_nanos": 162205664, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3570, + "fuse_op_other_nanos": 148849939, + "fuse_op_read_count": 227, + "fuse_op_read_nanos": 30079896, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 59157719, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 227, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17865, + "fuse_read_lane_wait_nanos": 1988515, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44314, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10262, + "fuse_write_lane_wait_nanos": 1721081, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41046, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6876, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46079, + "negative_lookup_count": 14920, + "path_cache_hits": 26119, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 1214311 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-5" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26130, + "attr_cache_misses": 30830, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 247, + "chunk_read_queries": 110, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32870, + "connection_wait_count": 32880, + "connection_wait_nanos": 12796984, + "dentry_cache_hits": 26119, + "dentry_cache_misses": 14052, + "fuse_adapter_attr_hits": 7164, + "fuse_adapter_attr_misses": 5590, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6732, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5368, + "fuse_adapter_negative_misses": 6732, + "fuse_callback_count": 31338, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12754, + "fuse_ino_file_resolutions": 25, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12110, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12754, + "fuse_op_getattr_nanos": 103017530, + "fuse_op_lookup_count": 12110, + "fuse_op_lookup_nanos": 162220872, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3582, + "fuse_op_other_nanos": 148859212, + "fuse_op_read_count": 227, + "fuse_op_read_nanos": 30079896, + "fuse_op_readdirplus_count": 1407, + "fuse_op_readdirplus_nanos": 59157719, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 227, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17865, + "fuse_read_lane_wait_nanos": 1988515, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1407, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44394, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10262, + "fuse_write_lane_wait_nanos": 1721081, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20698, + "lookup_base_count": 23, + "lookup_count": 41046, + "lookup_delta_count": 7545, + "lookup_whiteout_count": 0, + "negative_cache_hits": 6887, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46079, + "negative_lookup_count": 14920, + "path_cache_hits": 26119, + "path_cache_misses": 14052, + "path_component_count": 37632, + "path_resolution_count": 8140, + "readdir_count": 1, + "readdir_plus_count": 701, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 1214311 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-6" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32885, + "connection_wait_count": 32895, + "connection_wait_nanos": 12819793, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10262, + "fuse_write_lane_wait_nanos": 1721081, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 11, + "wal_checkpoint_nanos": 1214311 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "phase-checkpoint-7" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32888, + "connection_wait_count": 32898, + "connection_wait_nanos": 12825640, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10264, + "fuse_write_lane_wait_nanos": 1721880, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 1346034 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "agentfs" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32888, + "connection_wait_count": 32898, + "connection_wait_nanos": 12825640, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10264, + "fuse_write_lane_wait_nanos": 1721880, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 1346034 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "fuse_session" + }, + { + "counters": { + "agentfs_batcher_coalesced_ranges": 39, + "agentfs_batcher_commit_latency_ns_total": 942524849, + "agentfs_batcher_commit_txns": 4811, + "agentfs_batcher_drains_bytes": 0, + "agentfs_batcher_drains_explicit": 4695, + "agentfs_batcher_drains_timer": 15, + "agentfs_batcher_enqueues": 4749, + "agentfs_batcher_pending_max_bytes": 1442885, + "agentfs_batcher_txn_inodes_max": 4, + "agentfs_batcher_txn_inodes_total": 9409, + "attr_cache_hits": 26133, + "attr_cache_misses": 30832, + "base_fast_inode_invalidations": 15106, + "base_fast_open_eligible": 0, + "base_fast_open_keep_cache": 0, + "base_fast_open_passthrough_attempted": 0, + "base_fast_open_passthrough_fallback": 0, + "base_fast_open_passthrough_succeeded": 0, + "base_fast_open_rejected": 0, + "base_fast_stale_rejections": 0, + "chunk_read_chunks": 248, + "chunk_read_queries": 111, + "chunk_write_chunks": 982, + "connection_create_count": 10, + "connection_reuse_count": 32888, + "connection_wait_count": 32898, + "connection_wait_nanos": 12825640, + "dentry_cache_hits": 26123, + "dentry_cache_misses": 14054, + "fuse_adapter_attr_hits": 7208, + "fuse_adapter_attr_misses": 5591, + "fuse_adapter_entry_hits": 10, + "fuse_adapter_entry_misses": 6736, + "fuse_adapter_inval_entry_notifications": 58, + "fuse_adapter_inval_inode_notifications": 5506, + "fuse_adapter_lock_wait_count": 0, + "fuse_adapter_lock_wait_nanos": 0, + "fuse_adapter_negative_hits": 5583, + "fuse_adapter_negative_misses": 6736, + "fuse_callback_count": 31622, + "fuse_dispatch_inline_fallback": 0, + "fuse_dispatch_max_concurrent": 7, + "fuse_dispatch_parallel_tasks": 50, + "fuse_dispatch_wait_count": 50, + "fuse_dispatch_wait_nanos": 428998, + "fuse_exclusive_fallback_count": 0, + "fuse_flush_bytes": 53240674, + "fuse_flush_count": 4749, + "fuse_flush_ranges": 4749, + "fuse_getattr_count": 12799, + "fuse_ino_file_resolutions": 26, + "fuse_ino_file_upgrades": 0, + "fuse_keepcache_eligibility_drops": 0, + "fuse_keepcache_enabled": 1, + "fuse_lookup_count": 12329, + "fuse_noflush_enosys_replies": 1, + "fuse_noopen_enosys_replies": 1, + "fuse_op_create_count": 4703, + "fuse_op_create_nanos": 663784601, + "fuse_op_flush_count": 1, + "fuse_op_flush_nanos": 898, + "fuse_op_forget_count": 46, + "fuse_op_forget_nanos": 428632, + "fuse_op_getattr_count": 12799, + "fuse_op_getattr_nanos": 103121907, + "fuse_op_lookup_count": 12329, + "fuse_op_lookup_nanos": 162532805, + "fuse_op_open_count": 1, + "fuse_op_open_nanos": 932, + "fuse_op_other_count": 3618, + "fuse_op_other_nanos": 148876294, + "fuse_op_read_count": 229, + "fuse_op_read_nanos": 30786059, + "fuse_op_readdirplus_count": 1425, + "fuse_op_readdirplus_nanos": 60051328, + "fuse_op_setattr_count": 4773, + "fuse_op_setattr_nanos": 1331296195, + "fuse_op_write_count": 4839, + "fuse_op_write_nanos": 31882414, + "fuse_open_count": 1, + "fuse_pending_tail_drains": 0, + "fuse_read_count": 229, + "fuse_read_lane_max_concurrent": 6, + "fuse_read_lane_wait_count": 17880, + "fuse_read_lane_wait_nanos": 1993263, + "fuse_readdir_count": 0, + "fuse_readdir_plus_count": 1425, + "fuse_readdirplus_auto_enabled": 0, + "fuse_readdirplus_auto_requested": 0, + "fuse_readdirplus_do_enabled": 1, + "fuse_readdirplus_do_requested": 1, + "fuse_readdirplus_mode": 2, + "fuse_readdirplus_unsupported": 0, + "fuse_release_count": 0, + "fuse_sync_inval_entry_err": 0, + "fuse_sync_inval_entry_ok": 0, + "fuse_sync_inval_inode_err": 0, + "fuse_sync_inval_inode_ok": 0, + "fuse_sync_inval_latency_ns_total": 0, + "fuse_ttl_attr_ms": 10000, + "fuse_ttl_entry_ms": 10000, + "fuse_ttl_neg_ms": 1000, + "fuse_uring_requests": 44714, + "fuse_worker_queue_depth_peak": 2, + "fuse_workers_configured": 7, + "fuse_write_bytes": 53271481, + "fuse_write_count": 4839, + "fuse_write_lane_wait_count": 10264, + "fuse_write_lane_wait_nanos": 1721880, + "fuse_writeback_cache_enabled": 1, + "getattr_count": 20700, + "lookup_base_count": 23, + "lookup_count": 41054, + "lookup_delta_count": 7549, + "lookup_whiteout_count": 0, + "negative_cache_hits": 7102, + "negative_cache_invalidations": 10846, + "negative_cache_misses": 46088, + "negative_lookup_count": 14922, + "path_cache_hits": 26123, + "path_cache_misses": 14054, + "path_component_count": 37674, + "path_resolution_count": 8150, + "readdir_count": 1, + "readdir_plus_count": 710, + "wal_checkpoint_count": 13, + "wal_checkpoint_nanos": 1346034 + }, + "event": "agentfs_profile_summary", + "fallback_read_path": "hostfs", + "passthrough_supported": false, + "source": "run_parent" + } + ], + "returncode": 0, + "stderr_bytes": 40571, + "stderr_tail": "r_enqueues\":4749,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9409,\"attr_cache_hits\":26130,\"attr_cache_misses\":30830,\"base_fast_inode_invalidations\":15106,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":247,\"chunk_read_queries\":110,\"chunk_write_chunks\":982,\"connection_create_count\":10,\"connection_reuse_count\":32870,\"connection_wait_count\":32880,\"connection_wait_nanos\":12796984,\"dentry_cache_hits\":26119,\"dentry_cache_misses\":14052,\"fuse_adapter_attr_hits\":7164,\"fuse_adapter_attr_misses\":5590,\"fuse_adapter_entry_hits\":10,\"fuse_adapter_entry_misses\":6732,\"fuse_adapter_inval_entry_notifications\":58,\"fuse_adapter_inval_inode_notifications\":5506,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5368,\"fuse_adapter_negative_misses\":6732,\"fuse_callback_count\":31338,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":50,\"fuse_dispatch_wait_count\":50,\"fuse_dispatch_wait_nanos\":428998,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":53240674,\"fuse_flush_count\":4749,\"fuse_flush_ranges\":4749,\"fuse_getattr_count\":12754,\"fuse_ino_file_resolutions\":25,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12110,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":663784601,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":898,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":428632,\"fuse_op_getattr_count\":12754,\"fuse_op_getattr_nanos\":103017530,\"fuse_op_lookup_count\":12110,\"fuse_op_lookup_nanos\":162220872,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":932,\"fuse_op_other_count\":3582,\"fuse_op_other_nanos\":148859212,\"fuse_op_read_count\":227,\"fuse_op_read_nanos\":30079896,\"fuse_op_readdirplus_count\":1407,\"fuse_op_readdirplus_nanos\":59157719,\"fuse_op_setattr_count\":4773,\"fuse_op_setattr_nanos\":1331296195,\"fuse_op_write_count\":4839,\"fuse_op_write_nanos\":31882414,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":227,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17865,\"fuse_read_lane_wait_nanos\":1988515,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1407,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":44394,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":53271481,\"fuse_write_count\":4839,\"fuse_write_lane_wait_count\":10262,\"fuse_write_lane_wait_nanos\":1721081,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20698,\"lookup_base_count\":23,\"lookup_count\":41046,\"lookup_delta_count\":7545,\"lookup_whiteout_count\":0,\"negative_cache_hits\":6887,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46079,\"negative_lookup_count\":14920,\"path_cache_hits\":26119,\"path_cache_misses\":14052,\"path_component_count\":37632,\"path_resolution_count\":8140,\"readdir_count\":1,\"readdir_plus_count\":701,\"wal_checkpoint_count\":11,\"wal_checkpoint_nanos\":1214311},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"phase-checkpoint-6\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":39,\"agentfs_batcher_commit_latency_ns_total\":942524849,\"agentfs_batcher_commit_txns\":4811,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4695,\"agentfs_batcher_drains_timer\":15,\"agentfs_batcher_enqueues\":4749,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9409,\"attr_cache_hits\":26133,\"attr_cache_misses\":30832,\"base_fast_inode_invalidations\":15106,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":248,\"chunk_read_queries\":111,\"chunk_write_chunks\":982,\"connection_create_count\":10,\"connection_reuse_count\":32885,\"connection_wait_count\":32895,\"connection_wait_nanos\":12819793,\"dentry_cache_hits\":26123,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7208,\"fuse_adapter_attr_misses\":5591,\"fuse_adapter_entry_hits\":10,\"fuse_adapter_entry_misses\":6736,\"fuse_adapter_inval_entry_notifications\":58,\"fuse_adapter_inval_inode_notifications\":5506,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5583,\"fuse_adapter_negative_misses\":6736,\"fuse_callback_count\":31622,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":50,\"fuse_dispatch_wait_count\":50,\"fuse_dispatch_wait_nanos\":428998,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":53240674,\"fuse_flush_count\":4749,\"fuse_flush_ranges\":4749,\"fuse_getattr_count\":12799,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12329,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":663784601,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":898,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":428632,\"fuse_op_getattr_count\":12799,\"fuse_op_getattr_nanos\":103121907,\"fuse_op_lookup_count\":12329,\"fuse_op_lookup_nanos\":162532805,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":932,\"fuse_op_other_count\":3618,\"fuse_op_other_nanos\":148876294,\"fuse_op_read_count\":229,\"fuse_op_read_nanos\":30786059,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":60051328,\"fuse_op_setattr_count\":4773,\"fuse_op_setattr_nanos\":1331296195,\"fuse_op_write_count\":4839,\"fuse_op_write_nanos\":31882414,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":229,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17880,\"fuse_read_lane_wait_nanos\":1993263,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":44714,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":53271481,\"fuse_write_count\":4839,\"fuse_write_lane_wait_count\":10262,\"fuse_write_lane_wait_nanos\":1721081,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20700,\"lookup_base_count\":23,\"lookup_count\":41054,\"lookup_delta_count\":7549,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7102,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46088,\"negative_lookup_count\":14922,\"path_cache_hits\":26123,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":11,\"wal_checkpoint_nanos\":1214311},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"phase-checkpoint-7\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":39,\"agentfs_batcher_commit_latency_ns_total\":942524849,\"agentfs_batcher_commit_txns\":4811,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4695,\"agentfs_batcher_drains_timer\":15,\"agentfs_batcher_enqueues\":4749,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9409,\"attr_cache_hits\":26133,\"attr_cache_misses\":30832,\"base_fast_inode_invalidations\":15106,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":248,\"chunk_read_queries\":111,\"chunk_write_chunks\":982,\"connection_create_count\":10,\"connection_reuse_count\":32888,\"connection_wait_count\":32898,\"connection_wait_nanos\":12825640,\"dentry_cache_hits\":26123,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7208,\"fuse_adapter_attr_misses\":5591,\"fuse_adapter_entry_hits\":10,\"fuse_adapter_entry_misses\":6736,\"fuse_adapter_inval_entry_notifications\":58,\"fuse_adapter_inval_inode_notifications\":5506,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5583,\"fuse_adapter_negative_misses\":6736,\"fuse_callback_count\":31622,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":50,\"fuse_dispatch_wait_count\":50,\"fuse_dispatch_wait_nanos\":428998,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":53240674,\"fuse_flush_count\":4749,\"fuse_flush_ranges\":4749,\"fuse_getattr_count\":12799,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12329,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":663784601,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":898,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":428632,\"fuse_op_getattr_count\":12799,\"fuse_op_getattr_nanos\":103121907,\"fuse_op_lookup_count\":12329,\"fuse_op_lookup_nanos\":162532805,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":932,\"fuse_op_other_count\":3618,\"fuse_op_other_nanos\":148876294,\"fuse_op_read_count\":229,\"fuse_op_read_nanos\":30786059,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":60051328,\"fuse_op_setattr_count\":4773,\"fuse_op_setattr_nanos\":1331296195,\"fuse_op_write_count\":4839,\"fuse_op_write_nanos\":31882414,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":229,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17880,\"fuse_read_lane_wait_nanos\":1993263,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":44714,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":53271481,\"fuse_write_count\":4839,\"fuse_write_lane_wait_count\":10264,\"fuse_write_lane_wait_nanos\":1721880,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20700,\"lookup_base_count\":23,\"lookup_count\":41054,\"lookup_delta_count\":7549,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7102,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46088,\"negative_lookup_count\":14922,\"path_cache_hits\":26123,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":1346034},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"agentfs\"}\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":39,\"agentfs_batcher_commit_latency_ns_total\":942524849,\"agentfs_batcher_commit_txns\":4811,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4695,\"agentfs_batcher_drains_timer\":15,\"agentfs_batcher_enqueues\":4749,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9409,\"attr_cache_hits\":26133,\"attr_cache_misses\":30832,\"base_fast_inode_invalidations\":15106,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":248,\"chunk_read_queries\":111,\"chunk_write_chunks\":982,\"connection_create_count\":10,\"connection_reuse_count\":32888,\"connection_wait_count\":32898,\"connection_wait_nanos\":12825640,\"dentry_cache_hits\":26123,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7208,\"fuse_adapter_attr_misses\":5591,\"fuse_adapter_entry_hits\":10,\"fuse_adapter_entry_misses\":6736,\"fuse_adapter_inval_entry_notifications\":58,\"fuse_adapter_inval_inode_notifications\":5506,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5583,\"fuse_adapter_negative_misses\":6736,\"fuse_callback_count\":31622,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":50,\"fuse_dispatch_wait_count\":50,\"fuse_dispatch_wait_nanos\":428998,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":53240674,\"fuse_flush_count\":4749,\"fuse_flush_ranges\":4749,\"fuse_getattr_count\":12799,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12329,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":663784601,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":898,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":428632,\"fuse_op_getattr_count\":12799,\"fuse_op_getattr_nanos\":103121907,\"fuse_op_lookup_count\":12329,\"fuse_op_lookup_nanos\":162532805,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":932,\"fuse_op_other_count\":3618,\"fuse_op_other_nanos\":148876294,\"fuse_op_read_count\":229,\"fuse_op_read_nanos\":30786059,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":60051328,\"fuse_op_setattr_count\":4773,\"fuse_op_setattr_nanos\":1331296195,\"fuse_op_write_count\":4839,\"fuse_op_write_nanos\":31882414,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":229,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17880,\"fuse_read_lane_wait_nanos\":1993263,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":44714,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":53271481,\"fuse_write_count\":4839,\"fuse_write_lane_wait_count\":10264,\"fuse_write_lane_wait_nanos\":1721880,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20700,\"lookup_base_count\":23,\"lookup_count\":41054,\"lookup_delta_count\":7549,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7102,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46088,\"negative_lookup_count\":14922,\"path_cache_hits\":26123,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":1346034},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"fuse_session\"}\n\nSession: git-workload-e13728a82daa4c4a90c2edf69ec5b3f4\n\nTo resume this session:\n agentfs run --session git-workload-e13728a82daa4c4a90c2edf69ec5b3f4\n\nTo see what changed:\n agentfs diff git-workload-e13728a82daa4c4a90c2edf69ec5b3f4\n{\"counters\":{\"agentfs_batcher_coalesced_ranges\":39,\"agentfs_batcher_commit_latency_ns_total\":942524849,\"agentfs_batcher_commit_txns\":4811,\"agentfs_batcher_drains_bytes\":0,\"agentfs_batcher_drains_explicit\":4695,\"agentfs_batcher_drains_timer\":15,\"agentfs_batcher_enqueues\":4749,\"agentfs_batcher_pending_max_bytes\":1442885,\"agentfs_batcher_txn_inodes_max\":4,\"agentfs_batcher_txn_inodes_total\":9409,\"attr_cache_hits\":26133,\"attr_cache_misses\":30832,\"base_fast_inode_invalidations\":15106,\"base_fast_open_eligible\":0,\"base_fast_open_keep_cache\":0,\"base_fast_open_passthrough_attempted\":0,\"base_fast_open_passthrough_fallback\":0,\"base_fast_open_passthrough_succeeded\":0,\"base_fast_open_rejected\":0,\"base_fast_stale_rejections\":0,\"chunk_read_chunks\":248,\"chunk_read_queries\":111,\"chunk_write_chunks\":982,\"connection_create_count\":10,\"connection_reuse_count\":32888,\"connection_wait_count\":32898,\"connection_wait_nanos\":12825640,\"dentry_cache_hits\":26123,\"dentry_cache_misses\":14054,\"fuse_adapter_attr_hits\":7208,\"fuse_adapter_attr_misses\":5591,\"fuse_adapter_entry_hits\":10,\"fuse_adapter_entry_misses\":6736,\"fuse_adapter_inval_entry_notifications\":58,\"fuse_adapter_inval_inode_notifications\":5506,\"fuse_adapter_lock_wait_count\":0,\"fuse_adapter_lock_wait_nanos\":0,\"fuse_adapter_negative_hits\":5583,\"fuse_adapter_negative_misses\":6736,\"fuse_callback_count\":31622,\"fuse_dispatch_inline_fallback\":0,\"fuse_dispatch_max_concurrent\":7,\"fuse_dispatch_parallel_tasks\":50,\"fuse_dispatch_wait_count\":50,\"fuse_dispatch_wait_nanos\":428998,\"fuse_exclusive_fallback_count\":0,\"fuse_flush_bytes\":53240674,\"fuse_flush_count\":4749,\"fuse_flush_ranges\":4749,\"fuse_getattr_count\":12799,\"fuse_ino_file_resolutions\":26,\"fuse_ino_file_upgrades\":0,\"fuse_keepcache_eligibility_drops\":0,\"fuse_keepcache_enabled\":1,\"fuse_lookup_count\":12329,\"fuse_noflush_enosys_replies\":1,\"fuse_noopen_enosys_replies\":1,\"fuse_op_create_count\":4703,\"fuse_op_create_nanos\":663784601,\"fuse_op_flush_count\":1,\"fuse_op_flush_nanos\":898,\"fuse_op_forget_count\":46,\"fuse_op_forget_nanos\":428632,\"fuse_op_getattr_count\":12799,\"fuse_op_getattr_nanos\":103121907,\"fuse_op_lookup_count\":12329,\"fuse_op_lookup_nanos\":162532805,\"fuse_op_open_count\":1,\"fuse_op_open_nanos\":932,\"fuse_op_other_count\":3618,\"fuse_op_other_nanos\":148876294,\"fuse_op_read_count\":229,\"fuse_op_read_nanos\":30786059,\"fuse_op_readdirplus_count\":1425,\"fuse_op_readdirplus_nanos\":60051328,\"fuse_op_setattr_count\":4773,\"fuse_op_setattr_nanos\":1331296195,\"fuse_op_write_count\":4839,\"fuse_op_write_nanos\":31882414,\"fuse_open_count\":1,\"fuse_pending_tail_drains\":0,\"fuse_read_count\":229,\"fuse_read_lane_max_concurrent\":6,\"fuse_read_lane_wait_count\":17880,\"fuse_read_lane_wait_nanos\":1993263,\"fuse_readdir_count\":0,\"fuse_readdir_plus_count\":1425,\"fuse_readdirplus_auto_enabled\":0,\"fuse_readdirplus_auto_requested\":0,\"fuse_readdirplus_do_enabled\":1,\"fuse_readdirplus_do_requested\":1,\"fuse_readdirplus_mode\":2,\"fuse_readdirplus_unsupported\":0,\"fuse_release_count\":0,\"fuse_sync_inval_entry_err\":0,\"fuse_sync_inval_entry_ok\":0,\"fuse_sync_inval_inode_err\":0,\"fuse_sync_inval_inode_ok\":0,\"fuse_sync_inval_latency_ns_total\":0,\"fuse_ttl_attr_ms\":10000,\"fuse_ttl_entry_ms\":10000,\"fuse_ttl_neg_ms\":1000,\"fuse_uring_requests\":44714,\"fuse_worker_queue_depth_peak\":2,\"fuse_workers_configured\":7,\"fuse_write_bytes\":53271481,\"fuse_write_count\":4839,\"fuse_write_lane_wait_count\":10264,\"fuse_write_lane_wait_nanos\":1721880,\"fuse_writeback_cache_enabled\":1,\"getattr_count\":20700,\"lookup_base_count\":23,\"lookup_count\":41054,\"lookup_delta_count\":7549,\"lookup_whiteout_count\":0,\"negative_cache_hits\":7102,\"negative_cache_invalidations\":10846,\"negative_cache_misses\":46088,\"negative_lookup_count\":14922,\"path_cache_hits\":26123,\"path_cache_misses\":14054,\"path_component_count\":37674,\"path_resolution_count\":8150,\"readdir_count\":1,\"readdir_plus_count\":710,\"wal_checkpoint_count\":13,\"wal_checkpoint_nanos\":1346034},\"event\":\"agentfs_profile_summary\",\"fallback_read_path\":\"hostfs\",\"passthrough_supported\":false,\"source\":\"run_parent\"}\n", + "stdout_bytes": 19305, + "stdout_tail": "2026-07-03T11:38:41.505747Z INFO agentfs::fuser::session: resolved FUSE dispatch mode: parallel workers=7 queue_capacity=28\n2026-07-03T11:38:41.506007Z INFO agentfs::fuse: advertising FUSE_OVER_IO_URING\n2026-07-03T11:38:41.506222Z INFO agentfs::fuser::uring: starting fuse-over-io_uring queues nr_queues=14 depth=4\n2026-07-03T11:38:42.891344Z WARN agentfs::fuser::request: Request RequestId(22063): Failed to send reply: No such file or directory (os error 2)\n2026-07-03T11:38:43.891050Z WARN agentfs::fuser::request: Request RequestId(41239): Failed to send reply: No such file or directory (os error 2)\n2026-07-03T11:38:44.891046Z WARN agentfs::fuser::request: Request RequestId(62137): Failed to send reply: No such file or directory (os error 2)\n{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.019882690990925767, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work\", \"duration_seconds\": 0.005993513012072071, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work\", \"duration_seconds\": 0.006131766000180505, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work\", \"duration_seconds\": 0.007708599005127326, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.005307429004460573, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work\", \"duration_seconds\": 0.22109708399511874, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work\", \"duration_seconds\": 0.07473899800970685, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-9hklcrln/agentfs-base/mirror.git\", \"/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/agentfs-base\", \"duration_seconds\": 3.4367760949971853, \"returncode\": 0, \"stderr_bytes\": 2571, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\nUpdating files: 31% (1473/4644)\\nUpdating files: 32% (1487/4644)\\nUpdating files: 33% (1533/4644)\\nUpdating files: 34% (1579/4644)\\nUpdating files: 35% (1626/4644)\\nUpdating files: 36% (1672/4644)\\nUpdating files: 37% (1719/4644)\\nUpdating files: 38% (1765/4644)\\nUpdating files: 39% (1812/4644)\\nUpdating files: 40% (1858/4644)\\nUpdating files: 41% (1905/4644)\\nUpdating files: 42% (1951/4644)\\nUpdating files: 43% (1997/4644)\\nUpdating files: 44% (2044/4644)\\nUpdating files: 45% (2090/4644)\\nUpdating files: 46% (2137/4644)\\nUpdating files: 47% (2183/4644)\\nUpdating files: 48% (2230/4644)\\nUpdating files: 49% (2276/4644)\\nUpdating files: 50% (2322/4644)\\nUpdating files: 51% (2369/4644)\\nUpdating files: 52% (2415/4644)\\nUpdating files: 53% (2462/4644)\\nUpdating files: 54% (2508/4644)\\nUpdating files: 55% (2555/4644)\\nUpdating files: 56% (2601/4644)\\nUpdating files: 57% (2648/4644)\\nUpdating files: 58% (2694/4644)\\nUpdating files: 59% (2740/4644)\\nUpdating files: 60% (2787/4644)\\nUpdating files: 61% (2833/4644)\\nUpdating files: 62% (2880/4644)\\nUpdating files: 62% (2887/4644)\\nUpdating files: 63% (2926/4644)\\nUpdating files: 64% (2973/4644)\\nUpdating files: 65% (3019/4644)\\nUpdating files: 66% (3066/4644)\\nUpdating files: 67% (3112/4644)\\nUpdating files: 68% (3158/4644)\\nUpdating files: 69% (3205/4644)\\nUpdating files: 70% (3251/4644)\\nUpdating files: 71% (3298/4644)\\nUpdating files: 72% (3344/4644)\\nUpdating files: 73% (3391/4644)\\nUpdating files: 74% (3437/4644)\\nUpdating files: 75% (3483/4644)\\nUpdating files: 76% (3530/4644)\\nUpdating files: 77% (3576/4644)\\nUpdating files: 78% (3623/4644)\\nUpdating files: 79% (3669/4644)\\nUpdating files: 80% (3716/4644)\\nUpdating files: 81% (3762/4644)\\nUpdating files: 82% (3809/4644)\\nUpdating files: 83% (3855/4644)\\nUpdating files: 84% (3901/4644)\\nUpdating files: 85% (3948/4644)\\nUpdating files: 86% (3994/4644)\\nUpdating files: 87% (4041/4644)\\nUpdating files: 88% (4087/4644)\\nUpdating files: 89% (4134/4644)\\nUpdating files: 90% (4180/4644)\\nUpdating files: 91% (4227/4644)\\nUpdating files: 92% (4273/4644)\\nUpdating files: 93% (4319/4644)\\nUpdating files: 94% (4366/4644)\\nUpdating files: 95% (4412/4644)\\nUpdating files: 96% (4459/4644)\\nUpdating files: 96% (4500/4644)\\nUpdating files: 97% (4505/4644)\\nUpdating files: 98% (4552/4644)\\nUpdating files: 99% (4598/4644)\\nUpdating files: 100% (4644/4644)\\nUpdating files: 100% (4644/4644), done.\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work\", \"duration_seconds\": 0.017935351002961397, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work\", \"duration_seconds\": 0.11070463199575897, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.07745447699562646, \"clone\": 3.4368674799916334, \"diff\": 0.019882690990925767, \"edit\": 0.005307429004460573, \"fsck\": 0.22111140200286172, \"read_search\": 0.009250258008250967, \"status\": 0.1286685150116682}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work\", \"duration_seconds\": 0.005406737007433549, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 4.599304073009989}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.019882690990925767, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work", + "duration_seconds": 0.005993513012072071, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work", + "duration_seconds": 0.006131766000180505, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work", + "duration_seconds": 0.007708599005127326, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.005307429004460573, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work", + "duration_seconds": 0.22109708399511874, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work", + "duration_seconds": 0.07473899800970685, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-9hklcrln/agentfs-base/mirror.git", + "/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/agentfs-base", + "duration_seconds": 3.4367760949971853, + "returncode": 0, + "stderr_bytes": 2571, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\nUpdating files: 31% (1473/4644)\nUpdating files: 32% (1487/4644)\nUpdating files: 33% (1533/4644)\nUpdating files: 34% (1579/4644)\nUpdating files: 35% (1626/4644)\nUpdating files: 36% (1672/4644)\nUpdating files: 37% (1719/4644)\nUpdating files: 38% (1765/4644)\nUpdating files: 39% (1812/4644)\nUpdating files: 40% (1858/4644)\nUpdating files: 41% (1905/4644)\nUpdating files: 42% (1951/4644)\nUpdating files: 43% (1997/4644)\nUpdating files: 44% (2044/4644)\nUpdating files: 45% (2090/4644)\nUpdating files: 46% (2137/4644)\nUpdating files: 47% (2183/4644)\nUpdating files: 48% (2230/4644)\nUpdating files: 49% (2276/4644)\nUpdating files: 50% (2322/4644)\nUpdating files: 51% (2369/4644)\nUpdating files: 52% (2415/4644)\nUpdating files: 53% (2462/4644)\nUpdating files: 54% (2508/4644)\nUpdating files: 55% (2555/4644)\nUpdating files: 56% (2601/4644)\nUpdating files: 57% (2648/4644)\nUpdating files: 58% (2694/4644)\nUpdating files: 59% (2740/4644)\nUpdating files: 60% (2787/4644)\nUpdating files: 61% (2833/4644)\nUpdating files: 62% (2880/4644)\nUpdating files: 62% (2887/4644)\nUpdating files: 63% (2926/4644)\nUpdating files: 64% (2973/4644)\nUpdating files: 65% (3019/4644)\nUpdating files: 66% (3066/4644)\nUpdating files: 67% (3112/4644)\nUpdating files: 68% (3158/4644)\nUpdating files: 69% (3205/4644)\nUpdating files: 70% (3251/4644)\nUpdating files: 71% (3298/4644)\nUpdating files: 72% (3344/4644)\nUpdating files: 73% (3391/4644)\nUpdating files: 74% (3437/4644)\nUpdating files: 75% (3483/4644)\nUpdating files: 76% (3530/4644)\nUpdating files: 77% (3576/4644)\nUpdating files: 78% (3623/4644)\nUpdating files: 79% (3669/4644)\nUpdating files: 80% (3716/4644)\nUpdating files: 81% (3762/4644)\nUpdating files: 82% (3809/4644)\nUpdating files: 83% (3855/4644)\nUpdating files: 84% (3901/4644)\nUpdating files: 85% (3948/4644)\nUpdating files: 86% (3994/4644)\nUpdating files: 87% (4041/4644)\nUpdating files: 88% (4087/4644)\nUpdating files: 89% (4134/4644)\nUpdating files: 90% (4180/4644)\nUpdating files: 91% (4227/4644)\nUpdating files: 92% (4273/4644)\nUpdating files: 93% (4319/4644)\nUpdating files: 94% (4366/4644)\nUpdating files: 95% (4412/4644)\nUpdating files: 96% (4459/4644)\nUpdating files: 96% (4500/4644)\nUpdating files: 97% (4505/4644)\nUpdating files: 98% (4552/4644)\nUpdating files: 99% (4598/4644)\nUpdating files: 100% (4644/4644)\nUpdating files: 100% (4644/4644), done.\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work", + "duration_seconds": 0.017935351002961397, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work", + "duration_seconds": 0.11070463199575897, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.07745447699562646, + "clone": 3.4368674799916334, + "diff": 0.019882690990925767, + "edit": 0.005307429004460573, + "fsck": 0.22111140200286172, + "read_search": 0.009250258008250967, + "status": 0.1286685150116682 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/agentfs-base/work", + "duration_seconds": 0.005406737007433549, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 4.599304073009989 + } + }, + "base_tree": { + "after": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "222d8f6d80407304aaadf2f7f9b4544c86a0efec6d8864515a5f5c0f59dc8195", + "symlinks": 0 + }, + "before": { + "bytes": 9207621, + "directories": 10, + "files": 23, + "sha256": "222d8f6d80407304aaadf2f7f9b4544c86a0efec6d8864515a5f5c0f59dc8195", + "symlinks": 0 + }, + "unchanged": true + }, + "benchmark": "phase7-git-workload", + "command": { + "agentfs_prefix": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "run", + "--session", + "git-workload-e13728a82daa4c4a90c2edf69ec5b3f4", + "--no-default-allows", + "--" + ], + "argv": [ + "/home/ain3sh/factory/vfs/scripts/validation/git-workload-benchmark.py", + "--output", + "/home/ain3sh/factory/vfs/.agents/benchmarks/restructure-baseline/medians.json.iterations/warmup-00.json", + "--source", + ".agents/benchmarks/fixtures/codex", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8" + ], + "workload_argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ] + }, + "correctness": { + "agentfs_backup_verify": true, + "agentfs_base_unchanged": true, + "agentfs_db_inspectable": true, + "agentfs_integrity_require_portable": true, + "agentfs_no_nonempty_sidecars": true, + "agentfs_portable": true, + "agentfs_returncode_zero": true, + "equivalence": { + "agentfs": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + }, + "checked": true, + "equivalent": true, + "native": { + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ] + } + } + }, + "native_returncode_zero": true, + "passed": true, + "performance_passed": false + }, + "database": { + "after": { + "artifacts": [ + { + "bytes": 56549376, + "path": "/tmp/agentfs-git-workload-9hklcrln/home/.agentfs/run/git-workload-e13728a82daa4c4a90c2edf69ec5b3f4/delta.db" + } + ], + "path": "/tmp/agentfs-git-workload-9hklcrln/home/.agentfs/run/git-workload-e13728a82daa4c4a90c2edf69ec5b3f4/delta.db", + "total_bytes": 56549376 + }, + "backup": { + "artifacts": { + "artifacts": [ + { + "bytes": 56549376, + "path": "/tmp/agentfs-git-workload-9hklcrln/git-workload-backup.db" + } + ], + "path": "/tmp/agentfs-git-workload-9hklcrln/git-workload-backup.db", + "total_bytes": 56549376 + }, + "inspect": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "path": "/tmp/agentfs-git-workload-9hklcrln/git-workload-backup.db", + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "backup", + "/tmp/agentfs-git-workload-9hklcrln/home/.agentfs/run/git-workload-e13728a82daa4c4a90c2edf69ec5b3f4/delta.db", + "/tmp/agentfs-git-workload-9hklcrln/git-workload-backup.db", + "--verify" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln", + "duration_seconds": 2.6490465759998187, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 241, + "stdout_tail": "Source: /tmp/agentfs-git-workload-9hklcrln/home/.agentfs/run/git-workload-e13728a82daa4c4a90c2edf69ec5b3f4/delta.db\nBackup: /tmp/agentfs-git-workload-9hklcrln/git-workload-backup.db\nCheckpoint: complete\nCopy: complete\nVerification: complete\n", + "timed_out": false + } + }, + "inspect_after": { + "fs_chunk_override_rows": 0, + "fs_config": { + "chunk_size": "65536", + "inline_threshold": "16384", + "schema_version": "0.5" + }, + "fs_data_bytes": 41204221, + "fs_data_rows": 960, + "fs_inline_bytes": 11434794, + "fs_inode_rows": 5385, + "fs_origin_rows": 0, + "fs_partial_origin_rows": 0, + "fs_whiteout_rows": 0, + "inline_inode_rows": 4060, + "inspectable": true, + "portability_status": { + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true, + "stored_bytes": 52639015 + } + }, + "integrity": { + "result": { + "checks": [ + { + "detail": "ok", + "name": "pragma.integrity_check", + "ok": true, + "violating_rows": null + }, + { + "detail": "present", + "name": "schema.table.fs_config", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.fs_symlink", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.kv_store", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "present", + "name": "schema.table.tool_calls", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 0.5", + "name": "config.schema_version", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 65536", + "name": "config.chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 16384", + "name": "config.inline_threshold", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.kind_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_has_no_chunks", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunked_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_size_matches_blob", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.inline_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.non_regular_has_no_inline_data", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_reference_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunk_length_within_chunk_size", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "storage.chunks_only_regular_files", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "found 1, expected 1", + "name": "namespace.root_inode", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_parent_is_directory", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_target_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_root_inode_has_dentry", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.dentry_names_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.non_directory_nlink_matches_dentries", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "namespace.directory_nlink_positive", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.rows_reference_symlink_inodes", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "symlink.inodes_have_rows", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable: no partial-origin rows", + "name": "overlay.portability_status", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "portable requirement satisfied", + "name": "overlay.require_portable", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_delta_inode_regular", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_sizes_valid", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.partial_origin_paths_absolute", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_delta_inode_exists", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_nonnegative_index", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_references_partial_origin", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_unique", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.chunk_override_index_in_range", + "ok": true, + "violating_rows": 0 + }, + { + "detail": "0 violating rows", + "name": "overlay.whiteout_paths_absolute", + "ok": true, + "violating_rows": 0 + } + ], + "database": "/tmp/agentfs-git-workload-9hklcrln/home/.agentfs/run/git-workload-e13728a82daa4c4a90c2edf69ec5b3f4/delta.db", + "ok": true, + "origin_backed": false, + "partial_origin_rows": 0, + "portable": true + }, + "run": { + "argv": [ + "/home/ain3sh/factory/vfs/target/release/agentfs", + "integrity", + "/tmp/agentfs-git-workload-9hklcrln/home/.agentfs/run/git-workload-e13728a82daa4c4a90c2edf69ec5b3f4/delta.db", + "--json", + "--require-portable" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln", + "duration_seconds": 2.598406466000597, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 6395, + "stdout_tail": "{\n \"database\": \"/tmp/agentfs-git-workload-9hklcrln/home/.agentfs/run/git-workload-e13728a82daa4c4a90c2edf69ec5b3f4/delta.db\",\n \"ok\": true,\n \"portable\": true,\n \"origin_backed\": false,\n \"partial_origin_rows\": 0,\n \"checks\": [\n {\n \"name\": \"pragma.integrity_check\",\n \"ok\": true,\n \"detail\": \"ok\",\n \"violating_rows\": null\n },\n {\n \"name\": \"schema.table.fs_config\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_inode\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_dentry\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_data\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.fs_symlink\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.kv_store\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"schema.table.tool_calls\",\n \"ok\": true,\n \"detail\": \"present\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.schema_version\",\n \"ok\": true,\n \"detail\": \"found 0.5\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.chunk_size\",\n \"ok\": true,\n \"detail\": \"found 65536\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"config.inline_threshold\",\n \"ok\": true,\n \"detail\": \"found 16384\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.kind_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_has_no_chunks\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunked_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_size_matches_blob\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.inline_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.non_regular_has_no_inline_data\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_reference_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunk_length_within_chunk_size\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"storage.chunks_only_regular_files\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.root_inode\",\n \"ok\": true,\n \"detail\": \"found 1, expected 1\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_parent_is_directory\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_target_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_root_inode_has_dentry\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.dentry_names_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.non_directory_nlink_matches_dentries\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"namespace.directory_nlink_positive\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.rows_reference_symlink_inodes\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"symlink.inodes_have_rows\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.portability_status\",\n \"ok\": true,\n \"detail\": \"portable: no partial-origin rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.require_portable\",\n \"ok\": true,\n \"detail\": \"portable requirement satisfied\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_delta_inode_regular\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_sizes_valid\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.partial_origin_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_delta_inode_exists\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_nonnegative_index\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_references_partial_origin\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_unique\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.chunk_override_index_in_range\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n },\n {\n \"name\": \"overlay.whiteout_paths_absolute\",\n \"ok\": true,\n \"detail\": \"0 violating rows\",\n \"violating_rows\": 0\n }\n ]\n}\n", + "timed_out": false + } + }, + "nonempty_sidecars": false + }, + "environment": { + "AGENTFS_BIN": "/home/ain3sh/factory/vfs/target/release/agentfs", + "AGENTFS_PROFILE": "1" + }, + "git_commit": "ae1e13fcb1c52d5c57be051610a04aa9a318f2a5", + "kept_temp": false, + "native": { + "run": { + "argv": [ + "/usr/bin/python3", + "-c", + "\nimport argparse\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport subprocess\nimport time\nfrom pathlib import Path\n\n\nOUTPUT_TAIL_CHARS = 4000\n\n# Ordered phase labels emitted via profiling checkpoints (see profile_checkpoint).\nPROFILE_CHECKPOINTS = []\n\n\ndef profile_checkpoint(label):\n \"\"\"Request an AgentFS profiling checkpoint at a phase boundary.\n\n Only meaningful when running inside an AgentFS sandbox with profiling\n enabled. We signal the parent `agentfs run` process (SIGUSR1), which emits a\n cumulative, sequence-tagged profile summary to its stderr; the analyzer\n subtracts consecutive checkpoints to obtain per-phase counter deltas. A small\n sleep lets the parent flush before the next phase begins. Guarded on AGENTFS\n so native runs never signal the benchmark harness.\n \"\"\"\n PROFILE_CHECKPOINTS.append(label)\n if os.environ.get(\"AGENTFS\") != \"1\":\n return\n if os.environ.get(\"AGENTFS_PROFILE\", \"\") not in {\"1\", \"true\", \"TRUE\", \"yes\", \"on\"}:\n return\n try:\n os.kill(os.getppid(), signal.SIGUSR1)\n except OSError:\n return\n time.sleep(0.1)\n\n\ndef tail_text(value):\n if value is None:\n return \"\"\n if isinstance(value, bytes):\n text = value.decode(\"utf-8\", errors=\"replace\")\n else:\n text = str(value)\n if len(text) <= OUTPUT_TAIL_CHARS:\n return text\n return text[-OUTPUT_TAIL_CHARS:]\n\n\ndef git_env():\n env = os.environ.copy()\n env.setdefault(\"GIT_CONFIG_NOSYSTEM\", \"1\")\n env.setdefault(\"GIT_TERMINAL_PROMPT\", \"0\")\n env.setdefault(\"NO_COLOR\", \"1\")\n env.setdefault(\"LC_ALL\", \"C\")\n return env\n\n\ndef run_git(argv, cwd):\n started = time.perf_counter()\n proc = subprocess.run(\n [\"git\"] + argv,\n cwd=str(cwd),\n env=git_env(),\n text=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n return {\n \"argv\": [\"git\"] + argv,\n \"cwd\": str(cwd),\n \"duration_seconds\": time.perf_counter() - started,\n \"returncode\": proc.returncode,\n \"stdout_tail\": tail_text(proc.stdout),\n \"stderr_tail\": tail_text(proc.stderr),\n \"stdout_bytes\": len((proc.stdout or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stderr_bytes\": len((proc.stderr or \"\").encode(\"utf-8\", errors=\"replace\")),\n \"stdout\": proc.stdout,\n }\n\n\ndef require_ok(record, phase):\n if record[\"returncode\"] != 0:\n raise RuntimeError(\n f\"{phase} failed with exit {record['returncode']}: {record['stderr_tail']}\"\n )\n\n\ndef bounded_read_search(workdir, max_files, read_bytes, token):\n started = time.perf_counter()\n ls_files = run_git([\"ls-files\", \"-z\"], workdir)\n require_ok(ls_files, \"ls-files\")\n paths = [item for item in ls_files[\"stdout\"].split(\"\\0\") if item]\n digest = hashlib.sha256()\n scanned = 0\n bytes_read = 0\n matches = 0\n selected = []\n for rel in paths:\n if scanned >= max_files:\n break\n path = workdir / rel\n if not path.is_file():\n continue\n data = path.read_bytes()[:read_bytes]\n digest.update(rel.encode(\"utf-8\"))\n digest.update(b\"\\0\")\n digest.update(str(path.stat().st_size).encode(\"ascii\"))\n digest.update(b\"\\0\")\n digest.update(data)\n matches += data.count(token.encode(\"utf-8\"))\n bytes_read += len(data)\n scanned += 1\n selected.append(rel)\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"ls_files_run\": {key: value for key, value in ls_files.items() if key != \"stdout\"},\n \"digest\": digest.hexdigest(),\n \"files_total\": len(paths),\n \"files_scanned\": scanned,\n \"bytes_read\": bytes_read,\n \"token\": token,\n \"matches\": matches,\n \"selected_files\": selected,\n \"all_files\": paths,\n }\n\n\ndef representative_edit_paths(paths, limit):\n preferred_prefixes = (\"src/\", \"tests/\", \"docs/\")\n selected = []\n for prefix in preferred_prefixes:\n for rel in paths:\n if rel.startswith(prefix) and rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n for rel in paths:\n if rel not in selected:\n selected.append(rel)\n if len(selected) >= limit:\n return selected\n return selected\n\n\ndef edit_files(workdir, paths, limit):\n started = time.perf_counter()\n selected = representative_edit_paths(paths, limit)\n edits = []\n for index, rel in enumerate(selected):\n path = workdir / rel\n before_size = path.stat().st_size\n payload = f\"\\nAgentFS Git benchmark edit {index:02d} for {rel}\\n\".encode(\"utf-8\")\n with path.open(\"ab\", buffering=0) as handle:\n handle.write(payload)\n handle.flush()\n os.fsync(handle.fileno())\n edits.append(\n {\n \"path\": rel,\n \"size_before\": before_size,\n \"size_after\": path.stat().st_size,\n \"appended_bytes\": len(payload),\n }\n )\n return {\"duration_seconds\": time.perf_counter() - started, \"changed_files\": selected, \"edits\": edits}\n\n\ndef diff_summary(workdir):\n started = time.perf_counter()\n name_only = run_git([\"diff\", \"--name-only\", \"--\"], workdir)\n require_ok(name_only, \"diff --name-only\")\n stat = run_git([\"diff\", \"--stat\", \"--\"], workdir)\n require_ok(stat, \"diff --stat\")\n patch = run_git([\"diff\", \"--\", \".\"], workdir)\n require_ok(patch, \"diff\")\n changed = [line for line in name_only[\"stdout\"].splitlines() if line]\n patch_bytes = patch[\"stdout\"].encode(\"utf-8\", errors=\"replace\")\n return {\n \"duration_seconds\": time.perf_counter() - started,\n \"changed_files\": changed,\n \"changed_file_count\": len(changed),\n \"stat_stdout\": stat[\"stdout_tail\"],\n \"patch_sha256\": hashlib.sha256(patch_bytes).hexdigest(),\n \"patch_bytes\": len(patch_bytes),\n \"runs\": {\n \"name_only\": {key: value for key, value in name_only.items() if key != \"stdout\"},\n \"stat\": {key: value for key, value in stat.items() if key != \"stdout\"},\n \"patch\": {key: value for key, value in patch.items() if key != \"stdout\"},\n },\n }\n\n\ndef main(argv):\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--mirror\", default=\"mirror.git\")\n parser.add_argument(\"--work-dir\", default=\"work\")\n parser.add_argument(\"--read-files\", type=int, required=True)\n parser.add_argument(\"--read-bytes\", type=int, required=True)\n parser.add_argument(\"--edit-files\", type=int, required=True)\n parser.add_argument(\"--search-token\", default=\"AGENTFS_TOKEN\")\n parser.add_argument(\"--skip-fsck\", action=\"store_true\")\n args = parser.parse_args(argv)\n\n root = Path.cwd()\n mirror = root / args.mirror\n workdir = root / args.work_dir\n phase_seconds = {}\n phase_runs = {}\n started_total = time.perf_counter()\n\n started = time.perf_counter()\n clone = run_git([\"clone\", \"--local\", \"--no-hardlinks\", str(mirror), str(workdir)], root)\n require_ok(clone, \"clone\")\n phase_seconds[\"clone\"] = time.perf_counter() - started\n phase_runs[\"clone\"] = {key: value for key, value in clone.items() if key != \"stdout\"}\n profile_checkpoint(\"clone\")\n\n started = time.perf_counter()\n checkout = run_git([\"checkout\", \"-B\", \"agentfs-benchmark\"], workdir)\n require_ok(checkout, \"checkout\")\n head = run_git([\"rev-parse\", \"HEAD\"], workdir)\n require_ok(head, \"rev-parse\")\n phase_seconds[\"checkout\"] = time.perf_counter() - started\n phase_runs[\"checkout\"] = {key: value for key, value in checkout.items() if key != \"stdout\"}\n profile_checkpoint(\"checkout\")\n\n started = time.perf_counter()\n status_initial = run_git([\"status\", \"--short\"], workdir)\n require_ok(status_initial, \"status\")\n branch_status = run_git([\"status\", \"--short\", \"--branch\"], workdir)\n require_ok(branch_status, \"status --branch\")\n phase_seconds[\"status\"] = time.perf_counter() - started\n phase_runs[\"status\"] = {\n \"short\": {key: value for key, value in status_initial.items() if key != \"stdout\"},\n \"branch\": {key: value for key, value in branch_status.items() if key != \"stdout\"},\n }\n\n profile_checkpoint(\"status\")\n\n read_search = bounded_read_search(workdir, args.read_files, args.read_bytes, args.search_token)\n phase_seconds[\"read_search\"] = read_search[\"duration_seconds\"]\n profile_checkpoint(\"read_search\")\n\n edits = edit_files(workdir, read_search[\"all_files\"], args.edit_files)\n phase_seconds[\"edit\"] = edits[\"duration_seconds\"]\n profile_checkpoint(\"edit\")\n\n diff = diff_summary(workdir)\n phase_seconds[\"diff\"] = diff[\"duration_seconds\"]\n profile_checkpoint(\"diff\")\n\n fsck = {\"ran\": False, \"ok\": None, \"run\": None}\n if not args.skip_fsck:\n started = time.perf_counter()\n fsck_run = run_git([\"fsck\", \"--strict\"], workdir)\n phase_seconds[\"fsck\"] = time.perf_counter() - started\n fsck = {\n \"ran\": True,\n \"ok\": fsck_run[\"returncode\"] == 0,\n \"run\": {key: value for key, value in fsck_run.items() if key != \"stdout\"},\n }\n require_ok(fsck_run, \"fsck\")\n profile_checkpoint(\"fsck\")\n else:\n phase_seconds[\"fsck\"] = 0.0\n\n total_seconds = time.perf_counter() - started_total\n print(\n json.dumps(\n {\n \"head_commit\": head[\"stdout\"].strip(),\n \"phase_seconds\": phase_seconds,\n \"total_seconds\": total_seconds,\n \"phase_runs\": phase_runs,\n \"profile_checkpoints\": PROFILE_CHECKPOINTS,\n \"initial_status\": status_initial[\"stdout\"],\n \"branch_status\": branch_status[\"stdout\"],\n \"read_search\": {\n key: value\n for key, value in read_search.items()\n if key not in {\"duration_seconds\", \"all_files\"}\n },\n \"edits\": edits,\n \"diff\": diff,\n \"fsck\": fsck,\n },\n sort_keys=True,\n )\n )\n\n\ntry:\n main(sys.argv[1:])\nexcept Exception as exc:\n print(json.dumps({\"error\": str(exc)}, sort_keys=True))\n raise\n", + "--read-files", + "64", + "--read-bytes", + "4096", + "--edit-files", + "8", + "--search-token", + "AGENTFS_TOKEN" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/native", + "duration_seconds": 0.8465241599915316, + "profile_summaries": [], + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 15986, + "stdout_tail": "{\"branch_status\": \"## agentfs-benchmark\\n\", \"diff\": {\"changed_file_count\": 8, \"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.07674850200419314, \"patch_bytes\": 3282, \"patch_sha256\": \"51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc\", \"runs\": {\"name_only\": {\"argv\": [\"git\", \"diff\", \"--name-only\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/native/work\", \"duration_seconds\": 0.0273932860000059, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 144, \"stdout_tail\": \"docs/CLA.md\\ndocs/agents_md.md\\ndocs/authentication.md\\ndocs/config.md\\ndocs/contributing.md\\ndocs/example-config.md\\ndocs/exec.md\\ndocs/execpolicy.md\\n\"}, \"patch\": {\"argv\": [\"git\", \"diff\", \"--\", \".\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/native/work\", \"duration_seconds\": 0.025796036992687732, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 3282, \"stdout_tail\": \"diff --git a/docs/CLA.md b/docs/CLA.md\\nindex 804f202..3495ac9 100644\\n--- a/docs/CLA.md\\n+++ b/docs/CLA.md\\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\\n This Agreement is governed by the laws of the **State of California**, USA,\\n excluding its conflict\\u2011of\\u2011laws rules. If any provision is held unenforceable,\\n the remaining provisions remain in force.\\n+\\n+AgentFS Git benchmark edit 00 for docs/CLA.md\\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\\nindex 3df0fac..b8b063d 100644\\n--- a/docs/agents_md.md\\n+++ b/docs/agents_md.md\\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\\n ## Hierarchical agents message\\n \\n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\\n+\\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\\ndiff --git a/docs/authentication.md b/docs/authentication.md\\nindex c307349..b3bc9dc 100644\\n--- a/docs/authentication.md\\n+++ b/docs/authentication.md\\n@@ -1,3 +1,5 @@\\n # Authentication\\n \\n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\\n+\\n+AgentFS Git benchmark edit 02 for docs/authentication.md\\ndiff --git a/docs/config.md b/docs/config.md\\nindex d35b0a8..030e278 100644\\n--- a/docs/config.md\\n+++ b/docs/config.md\\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\\n still allowing managed hooks from requirements and managed config layers. This\\n setting is only supported in `requirements.toml`; putting it in `config.toml`\\n does not enable managed-hooks-only mode.\\n+\\n+AgentFS Git benchmark edit 03 for docs/config.md\\ndiff --git a/docs/contributing.md b/docs/contributing.md\\nindex aeae1f1..b5a22ac 100644\\n--- a/docs/contributing.md\\n+++ b/docs/contributing.md\\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\\n ### Security & responsible AI\\n \\n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\\n+\\n+AgentFS Git benchmark edit 04 for docs/contributing.md\\ndiff --git a/docs/example-config.md b/docs/example-config.md\\nindex 84b1143..b09f835 100644\\n--- a/docs/example-config.md\\n+++ b/docs/example-config.md\\n@@ -1,3 +1,5 @@\\n # Sample configuration\\n \\n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\\n+\\n+AgentFS Git benchmark edit 05 for docs/example-config.md\\ndiff --git a/docs/exec.md b/docs/exec.md\\nindex 57e4323..a81da98 100644\\n--- a/docs/exec.md\\n+++ b/docs/exec.md\\n@@ -1,3 +1,5 @@\\n # Non-interactive mode\\n \\n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\\n+\\n+AgentFS Git benchmark edit 06 for docs/exec.md\\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\\nindex cafebb3..3b48afe 100644\\n--- a/docs/execpolicy.md\\n+++ b/docs/execpolicy.md\\n@@ -1,3 +1,5 @@\\n # Execution policy\\n \\n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\\n+\\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\\n\"}, \"stat\": {\"argv\": [\"git\", \"diff\", \"--stat\", \"--\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/native/work\", \"duration_seconds\": 0.023508051002863795, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 283, \"stdout_tail\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}}, \"stat_stdout\": \" docs/CLA.md | 2 ++\\n docs/agents_md.md | 2 ++\\n docs/authentication.md | 2 ++\\n docs/config.md | 2 ++\\n docs/contributing.md | 2 ++\\n docs/example-config.md | 2 ++\\n docs/exec.md | 2 ++\\n docs/execpolicy.md | 2 ++\\n 8 files changed, 16 insertions(+)\\n\"}, \"edits\": {\"changed_files\": [\"docs/CLA.md\", \"docs/agents_md.md\", \"docs/authentication.md\", \"docs/config.md\", \"docs/contributing.md\", \"docs/example-config.md\", \"docs/exec.md\", \"docs/execpolicy.md\"], \"duration_seconds\": 0.0005141409928910434, \"edits\": [{\"appended_bytes\": 47, \"path\": \"docs/CLA.md\", \"size_after\": 2106, \"size_before\": 2059}, {\"appended_bytes\": 53, \"path\": \"docs/agents_md.md\", \"size_after\": 462, \"size_before\": 409}, {\"appended_bytes\": 58, \"path\": \"docs/authentication.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 50, \"path\": \"docs/config.md\", \"size_after\": 776, \"size_before\": 726}, {\"appended_bytes\": 56, \"path\": \"docs/contributing.md\", \"size_after\": 6380, \"size_before\": 6324}, {\"appended_bytes\": 58, \"path\": \"docs/example-config.md\", \"size_after\": 192, \"size_before\": 134}, {\"appended_bytes\": 48, \"path\": \"docs/exec.md\", \"size_after\": 194, \"size_before\": 146}, {\"appended_bytes\": 54, \"path\": \"docs/execpolicy.md\", \"size_after\": 192, \"size_before\": 138}]}, \"fsck\": {\"ok\": true, \"ran\": true, \"run\": {\"argv\": [\"git\", \"fsck\", \"--strict\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/native/work\", \"duration_seconds\": 0.22372002599877305, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}, \"head_commit\": \"7d47056ea42636271ac020b86347fbbef49490aa\", \"initial_status\": \"\", \"phase_runs\": {\"checkout\": {\"argv\": [\"git\", \"checkout\", \"-B\", \"agentfs-benchmark\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/native/work\", \"duration_seconds\": 0.08481002900225576, \"returncode\": 0, \"stderr_bytes\": 45, \"stderr_tail\": \"Switched to a new branch 'agentfs-benchmark'\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"clone\": {\"argv\": [\"git\", \"clone\", \"--local\", \"--no-hardlinks\", \"/tmp/agentfs-git-workload-9hklcrln/native/mirror.git\", \"/tmp/agentfs-git-workload-9hklcrln/native/work\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/native\", \"duration_seconds\": 0.316977031994611, \"returncode\": 0, \"stderr_bytes\": 149, \"stderr_tail\": \"Cloning into '/tmp/agentfs-git-workload-9hklcrln/native/work'...\\nwarning: source repository is shallow, ignoring --local\\nwarning: --local is ignored\\n\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}, \"status\": {\"branch\": {\"argv\": [\"git\", \"status\", \"--short\", \"--branch\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/native/work\", \"duration_seconds\": 0.03448833899165038, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 21, \"stdout_tail\": \"## agentfs-benchmark\\n\"}, \"short\": {\"argv\": [\"git\", \"status\", \"--short\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/native/work\", \"duration_seconds\": 0.03360859600070398, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 0, \"stdout_tail\": \"\"}}}, \"phase_seconds\": {\"checkout\": 0.08743785601109266, \"clone\": 0.3170567549968837, \"diff\": 0.07674850200419314, \"edit\": 0.0005141409928910434, \"fsck\": 0.22373121700366028, \"read_search\": 0.005972436993033625, \"status\": 0.06812123001145665}, \"profile_checkpoints\": [\"clone\", \"checkout\", \"status\", \"read_search\", \"edit\", \"diff\", \"fsck\"], \"read_search\": {\"bytes_read\": 117190, \"digest\": \"6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53\", \"files_scanned\": 64, \"files_total\": 4644, \"ls_files_run\": {\"argv\": [\"git\", \"ls-files\", \"-z\"], \"cwd\": \"/tmp/agentfs-git-workload-9hklcrln/native/work\", \"duration_seconds\": 0.0035966400027973577, \"returncode\": 0, \"stderr_bytes\": 0, \"stderr_tail\": \"\", \"stdout_bytes\": 263077, \"stdout_tail\": \"i_codex/api.py\\u0000sdk/python/src/openai_codex/async_client.py\\u0000sdk/python/src/openai_codex/client.py\\u0000sdk/python/src/openai_codex/errors.py\\u0000sdk/python/src/openai_codex/generated/__init__.py\\u0000sdk/python/src/openai_codex/generated/notification_registry.py\\u0000sdk/python/src/openai_codex/generated/v2_all.py\\u0000sdk/python/src/openai_codex/models.py\\u0000sdk/python/src/openai_codex/py.typed\\u0000sdk/python/src/openai_codex/retry.py\\u0000sdk/python/src/openai_codex/types.py\\u0000sdk/python/tests/app_server_harness.py\\u0000sdk/python/tests/app_server_helpers.py\\u0000sdk/python/tests/conftest.py\\u0000sdk/python/tests/test_app_server_approvals.py\\u0000sdk/python/tests/test_app_server_inputs.py\\u0000sdk/python/tests/test_app_server_lifecycle.py\\u0000sdk/python/tests/test_app_server_login.py\\u0000sdk/python/tests/test_app_server_run.py\\u0000sdk/python/tests/test_app_server_streaming.py\\u0000sdk/python/tests/test_app_server_turn_controls.py\\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\\u0000sdk/python/tests/test_async_client_behavior.py\\u0000sdk/python/tests/test_client_rpc_methods.py\\u0000sdk/python/tests/test_contract_generation.py\\u0000sdk/python/tests/test_public_api_runtime_behavior.py\\u0000sdk/python/tests/test_public_api_signatures.py\\u0000sdk/python/tests/test_real_app_server_integration.py\\u0000sdk/python/uv.lock\\u0000sdk/typescript/.prettierignore\\u0000sdk/typescript/.prettierrc\\u0000sdk/typescript/README.md\\u0000sdk/typescript/eslint.config.js\\u0000sdk/typescript/jest.config.cjs\\u0000sdk/typescript/package.json\\u0000sdk/typescript/samples/basic_streaming.ts\\u0000sdk/typescript/samples/helpers.ts\\u0000sdk/typescript/samples/structured_output.ts\\u0000sdk/typescript/samples/structured_output_zod.ts\\u0000sdk/typescript/src/codex.ts\\u0000sdk/typescript/src/codexOptions.ts\\u0000sdk/typescript/src/events.ts\\u0000sdk/typescript/src/exec.ts\\u0000sdk/typescript/src/index.ts\\u0000sdk/typescript/src/items.ts\\u0000sdk/typescript/src/outputSchemaFile.ts\\u0000sdk/typescript/src/thread.ts\\u0000sdk/typescript/src/threadOptions.ts\\u0000sdk/typescript/src/turnOptions.ts\\u0000sdk/typescript/tests/abort.test.ts\\u0000sdk/typescript/tests/codexExecSpy.ts\\u0000sdk/typescript/tests/exec.test.ts\\u0000sdk/typescript/tests/responsesProxy.ts\\u0000sdk/typescript/tests/run.test.ts\\u0000sdk/typescript/tests/runStreamed.test.ts\\u0000sdk/typescript/tests/setupCodexHome.ts\\u0000sdk/typescript/tests/testCodex.ts\\u0000sdk/typescript/tsconfig.json\\u0000sdk/typescript/tsup.config.ts\\u0000third_party/v8/BUILD.bazel\\u0000third_party/v8/README.md\\u0000third_party/v8/libcxx.BUILD.bazel\\u0000third_party/v8/libcxx_config/BUILD.bazel\\u0000third_party/v8/libcxx_config/__assertion_handler\\u0000third_party/v8/libcxx_config/__config_site\\u0000third_party/v8/libcxxabi.BUILD.bazel\\u0000third_party/v8/llvm_libc.BUILD.bazel\\u0000third_party/v8/rusty_v8_147_4_0.sha256\\u0000third_party/v8/v8_crate.BUILD.bazel\\u0000third_party/wezterm/LICENSE\\u0000tools/argument-comment-lint/.cargo/config.toml\\u0000tools/argument-comment-lint/.gitignore\\u0000tools/argument-comment-lint/BUILD.bazel\\u0000tools/argument-comment-lint/Cargo.lock\\u0000tools/argument-comment-lint/Cargo.toml\\u0000tools/argument-comment-lint/README.md\\u0000tools/argument-comment-lint/argument-comment-lint\\u0000tools/argument-comment-lint/driver.rs\\u0000tools/argument-comment-lint/lint_aspect.bzl\\u0000tools/argument-comment-lint/list-bazel-targets.sh\\u0000tools/argument-comment-lint/run-prebuilt-linter.py\\u0000tools/argument-comment-lint/run.py\\u0000tools/argument-comment-lint/rust-toolchain\\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\\u0000tools/argument-comment-lint/src/comment_parser.rs\\u0000tools/argument-comment-lint/src/lib.rs\\u0000tools/argument-comment-lint/test_wrapper_common.py\\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\\u0000tools/argument-comment-lint/ui/comment_matches.rs\\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\\u0000tools/argument-comment-lint/wrapper_common.py\\u0000workspace_root_test_launcher.bat.tpl\\u0000workspace_root_test_launcher.sh.tpl\\u0000\"}, \"matches\": 0, \"selected_files\": [\".bazelignore\", \".bazelrc\", \".bazelversion\", \".codespellignore\", \".codespellrc\", \".codex/environments/environment.toml\", \".codex/skills/babysit-pr/SKILL.md\", \".codex/skills/babysit-pr/agents/openai.yaml\", \".codex/skills/babysit-pr/references/github-api-notes.md\", \".codex/skills/babysit-pr/references/heuristics.md\", \".codex/skills/babysit-pr/scripts/gh_pr_watch.py\", \".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py\", \".codex/skills/code-review-breaking-changes/SKILL.md\", \".codex/skills/code-review-change-size/SKILL.md\", \".codex/skills/code-review-context/SKILL.md\", \".codex/skills/code-review-testing/SKILL.md\", \".codex/skills/code-review/SKILL.md\", \".codex/skills/codex-bug/SKILL.md\", \".codex/skills/codex-issue-digest/SKILL.md\", \".codex/skills/codex-issue-digest/agents/openai.yaml\", \".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py\", \".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py\", \".codex/skills/codex-pr-body/SKILL.md\", \".codex/skills/remote-tests/SKILL.md\", \".codex/skills/test-tui/SKILL.md\", \".codex/skills/update-v8-version/SKILL.md\", \".codex/skills/update-v8-version/agents/openai.yaml\", \".devcontainer/Dockerfile\", \".devcontainer/Dockerfile.secure\", \".devcontainer/README.md\", \".devcontainer/codex-install/package.json\", \".devcontainer/codex-install/pnpm-lock.yaml\", \".devcontainer/codex-install/pnpm-workspace.yaml\", \".devcontainer/devcontainer.json\", \".devcontainer/devcontainer.secure.json\", \".devcontainer/init-firewall.sh\", \".devcontainer/post-start.sh\", \".devcontainer/post_install.py\", \".gitattributes\", \".github/CODEOWNERS\", \".github/ISSUE_TEMPLATE/1-codex-app.yml\", \".github/ISSUE_TEMPLATE/2-extension.yml\", \".github/ISSUE_TEMPLATE/3-cli.yml\", \".github/ISSUE_TEMPLATE/4-bug-report.yml\", \".github/ISSUE_TEMPLATE/5-feature-request.yml\", \".github/ISSUE_TEMPLATE/6-docs-issue.yml\", \".github/actions/linux-code-sign/action.yml\", \".github/actions/macos-code-sign/action.yml\", \".github/actions/macos-code-sign/codex.entitlements.plist\", \".github/actions/macos-code-sign/notary_helpers.sh\", \".github/actions/prepare-bazel-ci/action.yml\", \".github/actions/run-argument-comment-lint/action.yml\", \".github/actions/setup-bazel-ci/action.yml\", \".github/actions/setup-msvc-env/action.yml\", \".github/actions/setup-msvc-env/setup-msvc-env.ps1\", \".github/actions/setup-rusty-v8/action.yml\", \".github/actions/windows-code-sign/action.yml\", \".github/blob-size-allowlist.txt\", \".github/codex-cli-splash.png\", \".github/codex/home/config.toml\", \".github/codex/labels/codex-attempt.md\", \".github/codex/labels/codex-review.md\", \".github/codex/labels/codex-rust-review.md\", \".github/codex/labels/codex-triage.md\"], \"token\": \"AGENTFS_TOKEN\"}, \"total_seconds\": 0.7797765090072062}\n", + "timed_out": false + }, + "workload": { + "branch_status": "## agentfs-benchmark\n", + "diff": { + "changed_file_count": 8, + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.07674850200419314, + "patch_bytes": 3282, + "patch_sha256": "51047bac747cb8ecfc865389e9d869d68bf5e0506710e02d42c98c029d61d3bc", + "runs": { + "name_only": { + "argv": [ + "git", + "diff", + "--name-only", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/native/work", + "duration_seconds": 0.0273932860000059, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 144, + "stdout_tail": "docs/CLA.md\ndocs/agents_md.md\ndocs/authentication.md\ndocs/config.md\ndocs/contributing.md\ndocs/example-config.md\ndocs/exec.md\ndocs/execpolicy.md\n" + }, + "patch": { + "argv": [ + "git", + "diff", + "--", + "." + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/native/work", + "duration_seconds": 0.025796036992687732, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 3282, + "stdout_tail": "diff --git a/docs/CLA.md b/docs/CLA.md\nindex 804f202..3495ac9 100644\n--- a/docs/CLA.md\n+++ b/docs/CLA.md\n@@ -47,3 +47,5 @@ entity under this CLA terminate.\n This Agreement is governed by the laws of the **State of California**, USA,\n excluding its conflict\u2011of\u2011laws rules. If any provision is held unenforceable,\n the remaining provisions remain in force.\n+\n+AgentFS Git benchmark edit 00 for docs/CLA.md\ndiff --git a/docs/agents_md.md b/docs/agents_md.md\nindex 3df0fac..b8b063d 100644\n--- a/docs/agents_md.md\n+++ b/docs/agents_md.md\n@@ -5,3 +5,5 @@ For information about AGENTS.md, see [this documentation](https://developers.ope\n ## Hierarchical agents message\n \n When the `child_agents_md` feature flag is enabled (via `[features]` in `config.toml`), Codex appends additional guidance about AGENTS.md scope and precedence to the user instructions message and emits that message even when no AGENTS.md is present.\n+\n+AgentFS Git benchmark edit 01 for docs/agents_md.md\ndiff --git a/docs/authentication.md b/docs/authentication.md\nindex c307349..b3bc9dc 100644\n--- a/docs/authentication.md\n+++ b/docs/authentication.md\n@@ -1,3 +1,5 @@\n # Authentication\n \n For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).\n+\n+AgentFS Git benchmark edit 02 for docs/authentication.md\ndiff --git a/docs/config.md b/docs/config.md\nindex d35b0a8..030e278 100644\n--- a/docs/config.md\n+++ b/docs/config.md\n@@ -13,3 +13,5 @@ Admins can set top-level `allow_managed_hooks_only = true` in\n still allowing managed hooks from requirements and managed config layers. This\n setting is only supported in `requirements.toml`; putting it in `config.toml`\n does not enable managed-hooks-only mode.\n+\n+AgentFS Git benchmark edit 03 for docs/config.md\ndiff --git a/docs/contributing.md b/docs/contributing.md\nindex aeae1f1..b5a22ac 100644\n--- a/docs/contributing.md\n+++ b/docs/contributing.md\n@@ -95,3 +95,5 @@ No special Git commands, email attachments, or commit footers required.\n ### Security & responsible AI\n \n Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.\n+\n+AgentFS Git benchmark edit 04 for docs/contributing.md\ndiff --git a/docs/example-config.md b/docs/example-config.md\nindex 84b1143..b09f835 100644\n--- a/docs/example-config.md\n+++ b/docs/example-config.md\n@@ -1,3 +1,5 @@\n # Sample configuration\n \n For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).\n+\n+AgentFS Git benchmark edit 05 for docs/example-config.md\ndiff --git a/docs/exec.md b/docs/exec.md\nindex 57e4323..a81da98 100644\n--- a/docs/exec.md\n+++ b/docs/exec.md\n@@ -1,3 +1,5 @@\n # Non-interactive mode\n \n For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).\n+\n+AgentFS Git benchmark edit 06 for docs/exec.md\ndiff --git a/docs/execpolicy.md b/docs/execpolicy.md\nindex cafebb3..3b48afe 100644\n--- a/docs/execpolicy.md\n+++ b/docs/execpolicy.md\n@@ -1,3 +1,5 @@\n # Execution policy\n \n For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).\n+\n+AgentFS Git benchmark edit 07 for docs/execpolicy.md\n" + }, + "stat": { + "argv": [ + "git", + "diff", + "--stat", + "--" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/native/work", + "duration_seconds": 0.023508051002863795, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 283, + "stdout_tail": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + } + }, + "stat_stdout": " docs/CLA.md | 2 ++\n docs/agents_md.md | 2 ++\n docs/authentication.md | 2 ++\n docs/config.md | 2 ++\n docs/contributing.md | 2 ++\n docs/example-config.md | 2 ++\n docs/exec.md | 2 ++\n docs/execpolicy.md | 2 ++\n 8 files changed, 16 insertions(+)\n" + }, + "edits": { + "changed_files": [ + "docs/CLA.md", + "docs/agents_md.md", + "docs/authentication.md", + "docs/config.md", + "docs/contributing.md", + "docs/example-config.md", + "docs/exec.md", + "docs/execpolicy.md" + ], + "duration_seconds": 0.0005141409928910434, + "edits": [ + { + "appended_bytes": 47, + "path": "docs/CLA.md", + "size_after": 2106, + "size_before": 2059 + }, + { + "appended_bytes": 53, + "path": "docs/agents_md.md", + "size_after": 462, + "size_before": 409 + }, + { + "appended_bytes": 58, + "path": "docs/authentication.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 50, + "path": "docs/config.md", + "size_after": 776, + "size_before": 726 + }, + { + "appended_bytes": 56, + "path": "docs/contributing.md", + "size_after": 6380, + "size_before": 6324 + }, + { + "appended_bytes": 58, + "path": "docs/example-config.md", + "size_after": 192, + "size_before": 134 + }, + { + "appended_bytes": 48, + "path": "docs/exec.md", + "size_after": 194, + "size_before": 146 + }, + { + "appended_bytes": 54, + "path": "docs/execpolicy.md", + "size_after": 192, + "size_before": 138 + } + ] + }, + "fsck": { + "ok": true, + "ran": true, + "run": { + "argv": [ + "git", + "fsck", + "--strict" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/native/work", + "duration_seconds": 0.22372002599877305, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + }, + "head_commit": "7d47056ea42636271ac020b86347fbbef49490aa", + "initial_status": "", + "phase_runs": { + "checkout": { + "argv": [ + "git", + "checkout", + "-B", + "agentfs-benchmark" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/native/work", + "duration_seconds": 0.08481002900225576, + "returncode": 0, + "stderr_bytes": 45, + "stderr_tail": "Switched to a new branch 'agentfs-benchmark'\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "clone": { + "argv": [ + "git", + "clone", + "--local", + "--no-hardlinks", + "/tmp/agentfs-git-workload-9hklcrln/native/mirror.git", + "/tmp/agentfs-git-workload-9hklcrln/native/work" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/native", + "duration_seconds": 0.316977031994611, + "returncode": 0, + "stderr_bytes": 149, + "stderr_tail": "Cloning into '/tmp/agentfs-git-workload-9hklcrln/native/work'...\nwarning: source repository is shallow, ignoring --local\nwarning: --local is ignored\n", + "stdout_bytes": 0, + "stdout_tail": "" + }, + "status": { + "branch": { + "argv": [ + "git", + "status", + "--short", + "--branch" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/native/work", + "duration_seconds": 0.03448833899165038, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 21, + "stdout_tail": "## agentfs-benchmark\n" + }, + "short": { + "argv": [ + "git", + "status", + "--short" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/native/work", + "duration_seconds": 0.03360859600070398, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 0, + "stdout_tail": "" + } + } + }, + "phase_seconds": { + "checkout": 0.08743785601109266, + "clone": 0.3170567549968837, + "diff": 0.07674850200419314, + "edit": 0.0005141409928910434, + "fsck": 0.22373121700366028, + "read_search": 0.005972436993033625, + "status": 0.06812123001145665 + }, + "profile_checkpoints": [ + "clone", + "checkout", + "status", + "read_search", + "edit", + "diff", + "fsck" + ], + "read_search": { + "bytes_read": 117190, + "digest": "6f4250781bcbc29fbb5cd6163f592bd1af1fe3bde784f25fb47d039ca363bb53", + "files_scanned": 64, + "files_total": 4644, + "ls_files_run": { + "argv": [ + "git", + "ls-files", + "-z" + ], + "cwd": "/tmp/agentfs-git-workload-9hklcrln/native/work", + "duration_seconds": 0.0035966400027973577, + "returncode": 0, + "stderr_bytes": 0, + "stderr_tail": "", + "stdout_bytes": 263077, + "stdout_tail": "i_codex/api.py\u0000sdk/python/src/openai_codex/async_client.py\u0000sdk/python/src/openai_codex/client.py\u0000sdk/python/src/openai_codex/errors.py\u0000sdk/python/src/openai_codex/generated/__init__.py\u0000sdk/python/src/openai_codex/generated/notification_registry.py\u0000sdk/python/src/openai_codex/generated/v2_all.py\u0000sdk/python/src/openai_codex/models.py\u0000sdk/python/src/openai_codex/py.typed\u0000sdk/python/src/openai_codex/retry.py\u0000sdk/python/src/openai_codex/types.py\u0000sdk/python/tests/app_server_harness.py\u0000sdk/python/tests/app_server_helpers.py\u0000sdk/python/tests/conftest.py\u0000sdk/python/tests/test_app_server_approvals.py\u0000sdk/python/tests/test_app_server_inputs.py\u0000sdk/python/tests/test_app_server_lifecycle.py\u0000sdk/python/tests/test_app_server_login.py\u0000sdk/python/tests/test_app_server_run.py\u0000sdk/python/tests/test_app_server_streaming.py\u0000sdk/python/tests/test_app_server_turn_controls.py\u0000sdk/python/tests/test_artifact_workflow_and_binaries.py\u0000sdk/python/tests/test_async_client_behavior.py\u0000sdk/python/tests/test_client_rpc_methods.py\u0000sdk/python/tests/test_contract_generation.py\u0000sdk/python/tests/test_public_api_runtime_behavior.py\u0000sdk/python/tests/test_public_api_signatures.py\u0000sdk/python/tests/test_real_app_server_integration.py\u0000sdk/python/uv.lock\u0000sdk/typescript/.prettierignore\u0000sdk/typescript/.prettierrc\u0000sdk/typescript/README.md\u0000sdk/typescript/eslint.config.js\u0000sdk/typescript/jest.config.cjs\u0000sdk/typescript/package.json\u0000sdk/typescript/samples/basic_streaming.ts\u0000sdk/typescript/samples/helpers.ts\u0000sdk/typescript/samples/structured_output.ts\u0000sdk/typescript/samples/structured_output_zod.ts\u0000sdk/typescript/src/codex.ts\u0000sdk/typescript/src/codexOptions.ts\u0000sdk/typescript/src/events.ts\u0000sdk/typescript/src/exec.ts\u0000sdk/typescript/src/index.ts\u0000sdk/typescript/src/items.ts\u0000sdk/typescript/src/outputSchemaFile.ts\u0000sdk/typescript/src/thread.ts\u0000sdk/typescript/src/threadOptions.ts\u0000sdk/typescript/src/turnOptions.ts\u0000sdk/typescript/tests/abort.test.ts\u0000sdk/typescript/tests/codexExecSpy.ts\u0000sdk/typescript/tests/exec.test.ts\u0000sdk/typescript/tests/responsesProxy.ts\u0000sdk/typescript/tests/run.test.ts\u0000sdk/typescript/tests/runStreamed.test.ts\u0000sdk/typescript/tests/setupCodexHome.ts\u0000sdk/typescript/tests/testCodex.ts\u0000sdk/typescript/tsconfig.json\u0000sdk/typescript/tsup.config.ts\u0000third_party/v8/BUILD.bazel\u0000third_party/v8/README.md\u0000third_party/v8/libcxx.BUILD.bazel\u0000third_party/v8/libcxx_config/BUILD.bazel\u0000third_party/v8/libcxx_config/__assertion_handler\u0000third_party/v8/libcxx_config/__config_site\u0000third_party/v8/libcxxabi.BUILD.bazel\u0000third_party/v8/llvm_libc.BUILD.bazel\u0000third_party/v8/rusty_v8_147_4_0.sha256\u0000third_party/v8/v8_crate.BUILD.bazel\u0000third_party/wezterm/LICENSE\u0000tools/argument-comment-lint/.cargo/config.toml\u0000tools/argument-comment-lint/.gitignore\u0000tools/argument-comment-lint/BUILD.bazel\u0000tools/argument-comment-lint/Cargo.lock\u0000tools/argument-comment-lint/Cargo.toml\u0000tools/argument-comment-lint/README.md\u0000tools/argument-comment-lint/argument-comment-lint\u0000tools/argument-comment-lint/driver.rs\u0000tools/argument-comment-lint/lint_aspect.bzl\u0000tools/argument-comment-lint/list-bazel-targets.sh\u0000tools/argument-comment-lint/run-prebuilt-linter.py\u0000tools/argument-comment-lint/run.py\u0000tools/argument-comment-lint/rust-toolchain\u0000tools/argument-comment-lint/src/bin/argument-comment-lint.rs\u0000tools/argument-comment-lint/src/comment_parser.rs\u0000tools/argument-comment-lint/src/lib.rs\u0000tools/argument-comment-lint/test_wrapper_common.py\u0000tools/argument-comment-lint/ui/allow_char_literals.rs\u0000tools/argument-comment-lint/ui/allow_string_literals.rs\u0000tools/argument-comment-lint/ui/comment_matches.rs\u0000tools/argument-comment-lint/ui/comment_matches_multiline.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.rs\u0000tools/argument-comment-lint/ui/comment_mismatch.stderr\u0000tools/argument-comment-lint/ui/ignore_external_methods.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.rs\u0000tools/argument-comment-lint/ui/uncommented_literal.stderr\u0000tools/argument-comment-lint/wrapper_common.py\u0000workspace_root_test_launcher.bat.tpl\u0000workspace_root_test_launcher.sh.tpl\u0000" + }, + "matches": 0, + "selected_files": [ + ".bazelignore", + ".bazelrc", + ".bazelversion", + ".codespellignore", + ".codespellrc", + ".codex/environments/environment.toml", + ".codex/skills/babysit-pr/SKILL.md", + ".codex/skills/babysit-pr/agents/openai.yaml", + ".codex/skills/babysit-pr/references/github-api-notes.md", + ".codex/skills/babysit-pr/references/heuristics.md", + ".codex/skills/babysit-pr/scripts/gh_pr_watch.py", + ".codex/skills/babysit-pr/scripts/test_gh_pr_watch.py", + ".codex/skills/code-review-breaking-changes/SKILL.md", + ".codex/skills/code-review-change-size/SKILL.md", + ".codex/skills/code-review-context/SKILL.md", + ".codex/skills/code-review-testing/SKILL.md", + ".codex/skills/code-review/SKILL.md", + ".codex/skills/codex-bug/SKILL.md", + ".codex/skills/codex-issue-digest/SKILL.md", + ".codex/skills/codex-issue-digest/agents/openai.yaml", + ".codex/skills/codex-issue-digest/scripts/collect_issue_digest.py", + ".codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py", + ".codex/skills/codex-pr-body/SKILL.md", + ".codex/skills/remote-tests/SKILL.md", + ".codex/skills/test-tui/SKILL.md", + ".codex/skills/update-v8-version/SKILL.md", + ".codex/skills/update-v8-version/agents/openai.yaml", + ".devcontainer/Dockerfile", + ".devcontainer/Dockerfile.secure", + ".devcontainer/README.md", + ".devcontainer/codex-install/package.json", + ".devcontainer/codex-install/pnpm-lock.yaml", + ".devcontainer/codex-install/pnpm-workspace.yaml", + ".devcontainer/devcontainer.json", + ".devcontainer/devcontainer.secure.json", + ".devcontainer/init-firewall.sh", + ".devcontainer/post-start.sh", + ".devcontainer/post_install.py", + ".gitattributes", + ".github/CODEOWNERS", + ".github/ISSUE_TEMPLATE/1-codex-app.yml", + ".github/ISSUE_TEMPLATE/2-extension.yml", + ".github/ISSUE_TEMPLATE/3-cli.yml", + ".github/ISSUE_TEMPLATE/4-bug-report.yml", + ".github/ISSUE_TEMPLATE/5-feature-request.yml", + ".github/ISSUE_TEMPLATE/6-docs-issue.yml", + ".github/actions/linux-code-sign/action.yml", + ".github/actions/macos-code-sign/action.yml", + ".github/actions/macos-code-sign/codex.entitlements.plist", + ".github/actions/macos-code-sign/notary_helpers.sh", + ".github/actions/prepare-bazel-ci/action.yml", + ".github/actions/run-argument-comment-lint/action.yml", + ".github/actions/setup-bazel-ci/action.yml", + ".github/actions/setup-msvc-env/action.yml", + ".github/actions/setup-msvc-env/setup-msvc-env.ps1", + ".github/actions/setup-rusty-v8/action.yml", + ".github/actions/windows-code-sign/action.yml", + ".github/blob-size-allowlist.txt", + ".github/codex-cli-splash.png", + ".github/codex/home/config.toml", + ".github/codex/labels/codex-attempt.md", + ".github/codex/labels/codex-review.md", + ".github/codex/labels/codex-rust-review.md", + ".github/codex/labels/codex-triage.md" + ], + "token": "AGENTFS_TOKEN" + }, + "total_seconds": 0.7797765090072062 + } + }, + "parameters": { + "edit_files": 8, + "fixture_dirs": 8, + "fixture_file_size_bytes": 1024, + "fixture_files": 96, + "read_bytes": 4096, + "read_files": 64, + "search_token": "AGENTFS_TOKEN", + "skip_fsck": false, + "timeout_seconds": 180.0 + }, + "schema_version": 1, + "source": { + "kind": "source", + "mirror_head": "7d47056ea42636271ac020b86347fbbef49490aa", + "path": "/home/ain3sh/factory/vfs/.agents/benchmarks/fixtures/codex" + }, + "summary": { + "agentfs_base_unchanged": true, + "agentfs_seconds": 4.599304073009989, + "all_equivalent": true, + "correctness_passed": true, + "native_seconds": 0.7797765090072062, + "passed": true, + "performance_passed": false, + "phase_ratios": { + "checkout": { + "agentfs_seconds": 0.07745447699562646, + "native_seconds": 0.08743785601109266, + "ratio": 0.8858231494811621 + }, + "clone": { + "agentfs_seconds": 3.4368674799916334, + "native_seconds": 0.3170567549968837, + "ratio": 10.839912494611301 + }, + "diff": { + "agentfs_seconds": 0.019882690990925767, + "native_seconds": 0.07674850200419314, + "ratio": 0.259062919427919 + }, + "edit": { + "agentfs_seconds": 0.005307429004460573, + "native_seconds": 0.0005141409928910434, + "ratio": 10.322905735674965 + }, + "fsck": { + "agentfs_seconds": 0.22111140200286172, + "native_seconds": 0.22373121700366028, + "ratio": 0.9882903466226812 + }, + "read_search": { + "agentfs_seconds": 0.009250258008250967, + "native_seconds": 0.005972436993033625, + "ratio": 1.5488247124315686 + }, + "status": { + "agentfs_seconds": 0.1286685150116682, + "native_seconds": 0.06812123001145665, + "ratio": 1.8888166727175757 + } + }, + "ratio": 5.898233686041298, + "threshold_failures": [ + { + "agentfs_seconds": 3.4368674799916334, + "native_seconds": 0.3170567549968837, + "phase": "clone", + "ratio": 10.839912494611301 + }, + { + "agentfs_seconds": 0.005307429004460573, + "native_seconds": 0.0005141409928910434, + "phase": "edit", + "ratio": 10.322905735674965 + } + ] + }, + "temp_dir": "/tmp/agentfs-git-workload-9hklcrln" +} diff --git a/.agents/benchmarks/restructure-baseline/provenance-post-m3.md b/.agents/benchmarks/restructure-baseline/provenance-post-m3.md new file mode 100644 index 00000000..de5eec3d --- /dev/null +++ b/.agents/benchmarks/restructure-baseline/provenance-post-m3.md @@ -0,0 +1,82 @@ +# Post-M3 Benchmark Rebaseline Provenance + +This file records the orchestrator-approved rebaseline for +`.agents/benchmarks/restructure-baseline/medians.json`. + +## Artifact changes + +- Archived M1 medians: `.agents/benchmarks/restructure-baseline/medians-m1.json` +- New active medians: `.agents/benchmarks/restructure-baseline/medians.json` +- New active raw iterations: `.agents/benchmarks/restructure-baseline/medians-post-m3.json.iterations/` +- Original M1 raw iterations remain in place at `.agents/benchmarks/restructure-baseline/medians.json.iterations/` + +## Provenance + +| Field | Value | +| --- | --- | +| Repository | `/home/ain3sh/factory/vfs` | +| Branch | `dev` | +| Commit | `2b811f03493e861e5a7786592d25ac73b39e1aba` | +| Produced at | `2026-07-04T01:14:37Z` | +| Fixture | `.agents/benchmarks/fixtures/codex` | +| AgentFS binary | `/home/ain3sh/factory/vfs/target/release/agentfs` | +| Build command | `cargo +nightly build --release --workspace --bins` | +| Benchmark command | `python3 scripts/validation/git-workload-benchmark-multi.py --label misc-a-rebaseline-run3 --iterations 5 --warmup 1 --agentfs-bin "$PWD/target/release/agentfs" --output /tmp/vfs-val/misc-a-bench-rebaseline/run3.json --keep-iterations --source .agents/benchmarks/fixtures/codex --read-files 64 --read-bytes 4096 --edit-files 8` | + +## Cached sentinel binary + +`bin/agentfs` is a prebuilt baseline-code sentinel binary for future BENCH +features that need an ambient-drift check. It was built from the same recorded +baseline commit, `2b811f03493e861e5a7786592d25ac73b39e1aba`, with: + +```bash +git worktree add --detach /tmp/vfs-restructure-baseline-bin. 2b811f03493e861e5a7786592d25ac73b39e1aba +CARGO_TARGET_DIR=/home/ain3sh/factory/vfs/target \ + cargo +nightly build --release --workspace --bins \ + --manifest-path /tmp/vfs-restructure-baseline-bin./Cargo.toml +install -m 0755 /home/ain3sh/factory/vfs/target/release/agentfs \ + /home/ain3sh/factory/vfs/.agents/benchmarks/restructure-baseline/bin/agentfs +``` + +The cached binary reports `agentfs v0.6.4-107-g2b811f0`. Its SHA-256 is: + +```text +3062585011a5a58da5b92781b808c58856be81125ee073b972513c4e4a3c0e53 .agents/benchmarks/restructure-baseline/bin/agentfs +``` + +## Load and hygiene checks + +All benchmark runs were serialized. Before each run, the worker checked load, +CPU count, active AgentFS mounts, and active AgentFS processes. Runs would have +aborted if the 1-minute load exceeded `2 * cores`. + +| Run | UTC start | Load average | Cores | Threshold | Result | +| --- | --- | ---: | ---: | ---: | --- | +| `run1` | `2026-07-04T01:10:45Z` | `8.00 6.23 6.32` | 14 | 28 | Completed, used as confirmation | +| `run2` | `2026-07-04T01:11:59Z` | `5.67 5.89 6.19` | 14 | 28 | Completed, discarded because it did not agree with `run1` or `run3` within band | +| `run3` | `2026-07-04T01:13:29Z` | `4.98 5.63 6.07` | 14 | 28 | Completed, recorded as the new active baseline | + +## Agreement checks + +The accepted pair is `run1` and `run3`. They agree within the perf-contract band +in both comparison directions: + +- `bench-compare.py run1.json run3.json`: 0 red phases +- `bench-compare.py run3.json run1.json`: 0 red phases + +After writing `run3` to `medians.json`, the active baseline was compared against +the current-HEAD confirmation run: + +- `bench-compare.py .agents/benchmarks/restructure-baseline/medians.json /tmp/vfs-val/misc-a-bench-rebaseline/run1.json`: 0 red phases + +## Recorded active medians + +| Phase | AgentFS median | +| --- | ---: | +| checkout | `56.7ms` | +| clone | `2800.9ms` | +| diff | `65.6ms` | +| edit | `5.0ms` | +| fsck | `215.0ms` | +| read_search | `8.8ms` | +| status | `178.0ms` | diff --git a/.agents/kernel/UPSTREAM-SUBMISSION.md b/.agents/kernel/UPSTREAM-SUBMISSION.md new file mode 100644 index 00000000..a9790cbb --- /dev/null +++ b/.agents/kernel/UPSTREAM-SUBMISSION.md @@ -0,0 +1,149 @@ +# FUSE STATX_BLOCKS flush-invalidation patch — upstream submission package + +Status: **VALIDATED, NOT YET SENT.** Everything below is what a future session +needs to pick this up and mail it to the maintainers. The only missing pieces +are the author's `Signed-off-by:` (DCO, must be added by the human author) and +the actual send. + +## The problem + +Under `writeback_cache`, `fuse_flush()` invalidates cached `STATX_BLOCKS` on +**every** `close(2)`, unconditionally — including read-only fds, and even when +`no_flush` is latched (the ENOSYS shortcut jumps to the same `inval_attr_out` +label). Since plain `stat(2)` requests the basic mask, which includes +`STATX_BLOCKS`, every stat-after-close forces a synchronous `FUSE_GETATTR` +round trip that `attr_timeout` was supposed to elide. + +Read-mostly workloads with open/read/close/stat patterns (build systems, +`git status`-style scanners) pay one GETATTR per file per cycle regardless of +the attribute timeout. For AgentFS this is the dominant residual on the warm +read path (measured ~2.1-2.4x native; stat-only variant runs at 1.3us/cycle +while stat+open/close runs at 12.1us/cycle with 32x the GETATTR traffic — +see the variant matrix in +`.agents/specs/2026-06-12-enosys-open-*.notes.md`, 2026-07-03 entries). + +## History (cite these in any discussion) + +- `cf576c58b3a2` ("fuse: invalidate inode attr in writeback cache mode", + v5.8, 2020): added the invalidation because `du` read `st_blocks == 0` + after a buffered write — i_blocks is never maintained by the kernel under + writeback cache, so flush-time invalidation forces a fresh GETATTR. +- `fa5eee57e33e` ("fuse: selective attribute invalidation", v5.16, Miklos): + narrowed the invalidation from full attrs to `STATX_BLOCKS` only. +- Nobody ever made it conditional on writes actually having happened. + +## The fix (17 lines) + +Key insight that makes the patch small: **i_blocks can only go stale through +the page cache.** Every other write path — direct I/O, writethrough, +`copy_file_range`, `fallocate` — already invalidates at write time via +`fuse_write_update_attr()` (`FUSE_STATX_MODSIZE` includes `STATX_BLOCKS`, +see `fs/fuse/fuse_i.h`). Page-cache dirtying has exactly two entry points: + +1. the iomap buffered-write branch of `fuse_cache_write_iter()` (the + `writeback` path), and +2. `fuse_page_mkwrite()` (mmap). + +So: add a `FUSE_I_BLOCKS_DIRTY` inode state bit, set it at those two sites, +and gate the `fuse_flush()` invalidation on `test_and_clear_bit()`. + +Why a per-inode bit and not `file->f_mode & FMODE_WRITE`: `fuse_flush()` +calls `write_inode_now()` unconditionally, so a read-only fd's close can +write back dirty pages produced through a *different* fd. The per-inode bit +handles that correctly (the writer's dirtying set the bit; whoever flushes +first invalidates and clears); an FMODE_WRITE gate would skip it. + +The motivating `du` case from cf576c58b3a2 is unaffected: a buffered write +sets the bit, so the writer's close still invalidates. + +## Artifacts + +- `0001-fuse-only-invalidate-STATX_BLOCKS-on-flush-if-pages-were-dirtied.patch` + (this directory) — format-patch output of kernel commit `85a047f20045`, + branch `fuse-blocks-dirty` in `~/src/linux` (shallow mainline clone, base + `51512e22e` / 7.2.0-rc1). If that tree is gone, `git am` the patch file + onto any recent mainline; it applies to the post-iomap-conversion + `fuse_cache_write_iter()`. +- `readpath-micro.py` (this directory) — the guest micro: 32 files x 32 + iterations of stat+open/read/close (the storm), plus two correctness + assertions (st_blocks fresh after a 1MB buffered write and after an 8KB + mmap write through page_mkwrite). + +## Validation (2026-07-03, virtme-ng 1.41, mainline 7.2.0-rc1, same tree +- patch) + +Guest: `vng --cpus 8 -m 4G`, config fragment `CONFIG_FUSE_FS=y`, +`CONFIG_FUSE_IO_URING=y`, `CONFIG_FUSE_PASSTHROUGH=y`. Workload: agentfs +(FUSE, writeback_cache, attr_timeout=10s) running `readpath-micro.py` with +`AGENTFS_PROFILE=1`, GETATTR counts from the profile summary. + +| metric | unpatched | patched | +|---------------------------|-----------|---------| +| storm cycle time | 18.72us | 8.51us (2.2x) | +| FUSE_GETATTR count | 1095 | 70 (15.6x fewer) | +| st_blocks after 1MB write | 2048 ok | 2048 ok | +| st_blocks after mmap write| 16 ok | 16 ok | + +Repro procedure end to end: + +```sh +# host, in the kernel tree +vng -b -f fuse.config # build (needs bc in PATH) +vng --cpus 8 -m 4G -- bash ab-guest.sh # boot + run micro, note counts +git am 0001-fuse-*.patch # or checkout fuse-blocks-dirty +vng -b -f fuse.config # incremental rebuild (~2 min) +vng --cpus 8 -m 4G -- bash ab-guest.sh # compare +``` + +where `ab-guest.sh` mounts an agentfs db in the guest's tmpfs /tmp and runs +`AGENTFS_PROFILE=1 agentfs exec g.db python3 -- readpath-micro.py`, then +greps `fuse_op_getattr_count` from the profile summary on stderr. + +A reproducer independent of agentfs (if maintainers ask): any libfuse +filesystem with writeback cache enabled (e.g. `passthrough_hp --o writeback`) +plus the same stat+open/read/close loop; count GETATTRs server-side. + +## checkpatch status + +`./scripts/checkpatch.pl --git HEAD`: clean except +1. `Missing Signed-off-by` — intentional, the author must add their own DCO + line (`git commit --amend -s` as the author identity), and +2. two "Unknown commit id" warnings for cf576c58b3a2 / fa5eee57e33e — false + positives from the shallow clone (depth 1); they resolve in a full clone. + +## Routing (from `./scripts/get_maintainer.pl`) + +- To: Miklos Szeredi (maintainer, FUSE FILESYSTEM CORE) +- Cc: fuse-devel@lists.linux.dev (fuse list) +- Cc: linux-kernel@vger.kernel.org +- Optionally Cc: linux-fsdevel@vger.kernel.org (VFS-adjacent attr caching) + +Target tree: Miklos' fuse.git `for-next` +(git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse.git). Rebase the +patch there before sending; conflicts are unlikely (the touched hunks are +stable), but the iomap write path is recent, so confirm +`fuse_cache_write_iter()` still has the `writeback` branch. + +## Remaining steps to actually send (in order) + +1. Decide author identity (patch currently authored as + `ain3sh `; a real full name is expected for + DCO — amend author + add matching `Signed-off-by`). +2. Rebase onto current fuse.git for-next; re-run checkpatch; re-run the vng + A/B if the surrounding code moved. +3. Send: `git send-email --to='Miklos Szeredi ' + --cc=fuse-devel@lists.linux.dev --cc=linux-kernel@vger.kernel.org + 0001-*.patch` (or `b4 send` if b4 is configured). Plain patch mail, no + cover letter needed for a single patch — the changelog carries the + argument. Expect review feedback on (a) whether a state bit vs an + FMODE_WRITE check is preferred, and (b) whether the bit should also be + set in the writethrough path for belt-and-braces (argue: not needed, + fuse_write_update_attr already invalidates there). +4. Watch https://lore.kernel.org/fuse-devel/ for replies. + +## Why we care (impact on this repo, for the eventual v2/benchmarks section) + +With this patch, agentfs' warm read path drops toward the persistent-fd +profile (stat-only measured 1.3us/cycle vs the 12.1us stat+open/close storm) +and the edit phase loses its 2 forced GETATTRs per edit — the two remaining +threshold misses that were classified "kernel floor" in +`.agents/specs/2026-06-11-per-phase-1-5x-roadmap-*.md`. diff --git a/.agents/kernel/ab-guest.sh b/.agents/kernel/ab-guest.sh new file mode 100755 index 00000000..dddd7067 --- /dev/null +++ b/.agents/kernel/ab-guest.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Runs inside the vng guest: mount agentfs, run the read-path micro, report +# GETATTR counts from the profile summary. +set -e +BIN=/home/ain3sh/factory/vfs/cli/target/release/agentfs +cd /tmp +uname -r +rm -f g.db && touch g.db +AGENTFS_PROFILE=1 $BIN exec g.db python3 -- /home/ain3sh/src/guest-ab/readpath-micro.py 2>/tmp/prof.log +python3 - <<'EOF' +import json, re +best = None +for line in open('/tmp/prof.log'): + if 'agentfs_profile_summary' in line and 'fuse_session' in line: + m = re.search(r'\{.*\}', line) + if m: + best = json.loads(m.group(0)) +c = (best or {}).get('counters', best) or {} +for op in ('getattr', 'open', 'flush', 'lookup'): + n = c.get(f'fuse_op_{op}_count', 0) + ns = c.get(f'fuse_op_{op}_nanos', 0) + print(f"fuse_{op}: count={n} total_ms={ns/1e6:.1f}") +EOF diff --git a/.agents/kernel/fuse.config b/.agents/kernel/fuse.config new file mode 100644 index 00000000..e507be51 --- /dev/null +++ b/.agents/kernel/fuse.config @@ -0,0 +1,3 @@ +CONFIG_FUSE_FS=y +CONFIG_FUSE_IO_URING=y +CONFIG_FUSE_PASSTHROUGH=y diff --git a/.claude/commands/generate-py-sdk.md b/.claude/commands/generate-py-sdk.md deleted file mode 100644 index cd8bf4e5..00000000 --- a/.claude/commands/generate-py-sdk.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -allowed-tools: Bash(git commit:*), Bash(git add:*), Bash(git status:*), Bash(mkdir:*), Bash(uv:*), Read, Edit(sdk/python/**), Write(sdk/python/**), Edit(.github/workflows/python.yml), Write(.github/workflows/python.yml), Write(.github/workflows/release.yml), Edit(.github/workflows/release.yml) -argument-hint: [ts-change-sha-commit] -description: Generate Python SDK for agentfs based on the Typescript SDK ---- - -## Dev rules - -- FRESH RULES from this file have higher priority than any other rules if they conflict -- YOU MUST COMMIT your changes FREQUENTLY DURING the process with compact but informative message with the motivation for the change and its high level description - - Don't hesitate to commit partial progress -- USE `uv` with `--directory sdk/python` command in order to avoid `cd` to the subdirectory -- ALWAYS USE pathes relative to the project root -- DO NOT EVER `cd` into the directories - tool permissions will not be validated properly -- USE ONLY SIMPLE "ls", "grep", "find", "cat" Bash commands and native Claude Code tools - otherwise permission will be blocked -- DOCUMENT tricky and hacky moments in the code - -## Context - -- Last time, python sdk was updated based on the comment $1 - - - If value is "unspecified" then regenerate SDK from scratch - - If value is set - FOCUS on the diff between the current state and specified commit hash - - The primary changes are in the Typescript SDK but changes outside of it also can contribute to the process - - For example, command prompt in .claude directory influence process heavily - -- You must generate Python SDK with the API similar to the current Typescript SDK located at ../../sdk/typescript -- The package name is `agentfs-sdk` and import path must be `agentfs_sdk` -- You must transfer all tests from Typescript SDK to the Python -- Use `turso.aio` python package which provide API similar to `aiosqlite` -- Use simple setup with builtin uv ruff formatter -- Use pytest for testing -- Use ty for type checking -- Maintain CI for linting and checking at .github/workflows/python.yml similar to the TS workflow at .github/workflows/typescript.yml -- Maintain CI for publishing the Python package to the PyPI in the .github/workflows/release.yml - - Use `PYPI_API_TOKEN` secret -- In the agetnfs-sdk implementation always explicitly close cursor or use it as context manager -- The SDK must work properly when CDC is enabled for tursodb: cover this scenario with additional test suite - - Execute `PRAGMA unstable_capture_data_changes_conn('full')` pragma to enable CDC for connection - -```py -class Connection: - def __init__(self, connector: Callable[[], BlockingConnection]) -> None: - async def close(self) -> None: - def __await__(self): - async def __aenter__(self) -> "Connection": - async def __aexit__(self, exc_type, exc, tb) -> None: - def cursor(self, factory: Optional[Callable[[BlockingConnection], BlockingCursor]] = None) -> "Cursor": - async def execute(self, sql: str, parameters: Sequence[Any] | Mapping[str, Any] = ()) -> "Cursor": - async def executemany(self, sql: str, parameters: Iterable[Sequence[Any] | Mapping[str, Any]]) -> "Cursor": - async def executescript(self, sql_script: str) -> "Cursor": - async def commit(self) -> None: - async def rollback(self) -> None: -class Cursor: - async def close(self) -> None: - # named parameters not supported at the moment - async def execute(self, sql: str, parameters: Sequence[Any] | Mapping[str, Any] = ()) -> "Cursor": - async def executemany(self, sql: str, parameters: Iterable[Sequence[Any] | Mapping[str, Any]]) -> "Cursor": - async def executescript(self, sql_script: str) -> "Cursor": - async def fetchone(self) -> Any: - async def fetchmany(self, size: Optional[int] = None) -> list[Any]: - async def fetchall(self) -> list[Any]: - async def __aenter__(self) -> "Cursor": - async def __aexit__(self, exc_type, exc, tb) -> None: - -# as Connection is awaitable - caller can use await connect(...) -def connect(database: str) -> Connection: -``` diff --git a/.claude/commands/refactor-py-sdk.md b/.claude/commands/refactor-py-sdk.md deleted file mode 100644 index a27b5728..00000000 --- a/.claude/commands/refactor-py-sdk.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -allowed-tools: Bash(git commit:*), Bash(git add:*), Bash(git status:*), Bash(mkdir:*), Bash(uv:*), Read, Edit(sdk/python/**), Write(sdk/python/**), Bash(grep:*), Bash(sed:*) -argument-hint: [prompt] -description: Command to refactor Python SDK ---- - -## Dev rules - -- COMMIT your changes in the end with detailed message with the motivation of changes and traces of your actions -- USE `uv` with `--directory sdk/python` command in order to avoid `cd` to the subdirectory -- ALWAYS USE pathes relative to the project root -- DO NOT EVER `cd` into the directories - tool permissions will not be validated properly -- USE ONLY SIMPLE "ls", "grep", "find", "cat" Bash commands and native Claude Code tools - otherwise permission will be blocked -- FORMAT AND TEST code with uv - -## Task - -$1 - -## Context - -- You must refactor Python SDK with the API similar to the current Typescript SDK located at ../../sdk/typescript -- The Python API should be similar to the Typescript one if possible - so inspect Typescript code before introducing any breaking change in the PUBLIC API diff --git a/.github/workflows/build-setup.yml b/.github/workflows/build-setup.yml deleted file mode 100644 index 739a7906..00000000 --- a/.github/workflows/build-setup.yml +++ /dev/null @@ -1,2 +0,0 @@ -- name: Install system dependencies - run: ./scripts/install-deps.sh diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml deleted file mode 100644 index b2ae2acb..00000000 --- a/.github/workflows/python.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: Python CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - test: - name: Test - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest] - python-version: ["3.12", "3.13"] - defaults: - run: - working-directory: sdk/python - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - version: "latest" - - - name: Install dependencies - run: uv sync - - - name: Run tests - run: uv run pytest - - check: - name: Check - runs-on: ubuntu-latest - defaults: - run: - working-directory: sdk/python - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - version: "latest" - - - name: Install dependencies - run: uv sync - - - name: Run linting - run: uv run ruff check . - - - name: Run formatting check - run: uv run ruff format --check . - - - name: Run type checking - run: uv run ty check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0996a632..c63cdcb1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -281,36 +281,6 @@ jobs: gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* - # Publish TypeScript SDK to npm - publish-npm: - needs: - - plan - - host - if: ${{ needs.plan.outputs.publishing == 'true' && needs.host.result == 'success' }} - runs-on: "ubuntu-22.04" - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - submodules: recursive - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - name: Update npm - run: npm install -g npm@11 - - name: Install dependencies - working-directory: sdk/typescript - run: npm install - - name: Build SDK - working-directory: sdk/typescript - run: npm run build - - name: Publish to npm - working-directory: sdk/typescript - env: - TAG_FLAG: "${{ fromJson(needs.host.outputs.val).announcement_is_prerelease && '--tag next' || '' }}" - run: npm publish --provenance --access public $TAG_FLAG - # Publish Rust SDK to crates.io publish-cargo: needs: @@ -331,42 +301,13 @@ jobs: env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - # Publish Python SDK to PyPI - publish-pypi: - needs: - - plan - - host - if: ${{ needs.plan.outputs.publishing == 'true' && needs.host.result == 'success' }} - runs-on: "ubuntu-22.04" - env: - UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - submodules: recursive - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - enable-cache: true - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - name: Build package - run: uv build --directory sdk/python - - name: Publish to PyPI - run: uv publish --directory sdk/python - announce: needs: - plan - host - - publish-npm - publish-cargo - - publish-pypi # use "always() && ..." to allow us to wait for all publish jobs while - # still allowing individual publish jobs to skip themselves (for prereleases). + # still allowing publish jobs to skip themselves (for prereleases). # "host" however must run to completion, no skipping allowed! if: ${{ always() && needs.host.result == 'success' }} runs-on: "ubuntu-22.04" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1334166e..89453d5e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -8,30 +8,27 @@ on: env: CARGO_TERM_COLOR: always + RUST_TOOLCHAIN: nightly-2026-05-08 jobs: - test: - name: Test (${{ matrix.os }}, ${{ matrix.project }}) + workspace: + name: Workspace (${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - project: [cli, sdk/rust] - defaults: - run: - working-directory: ${{ matrix.project }} + os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@v4 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@master with: - toolchain: nightly + toolchain: ${{ env.RUST_TOOLCHAIN }} components: rustfmt, clippy - name: Install system dependencies - if: matrix.project == 'cli' && (matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest') - run: ../scripts/install-deps.sh + if: matrix.os == 'ubuntu-latest' + run: ./scripts/install-deps.sh - name: Cache cargo registry uses: actions/cache@v4 @@ -48,40 +45,63 @@ jobs: - name: Cache cargo build uses: actions/cache@v4 with: - path: ${{ matrix.project }}/target - key: ${{ runner.os }}-cargo-build-target-${{ matrix.project }}-${{ hashFiles('**/Cargo.lock') }} + path: target + key: ${{ runner.os }}-cargo-build-target-workspace-${{ hashFiles('Cargo.lock') }} - name: Check formatting if: matrix.os == 'ubuntu-latest' - run: cargo fmt -- --check + run: cargo +"$RUST_TOOLCHAIN" fmt --all -- --check - name: Run clippy - if: matrix.os == 'ubuntu-latest' - run: cargo clippy -- -D warnings + run: cargo +"$RUST_TOOLCHAIN" clippy --workspace --all-targets -- -D warnings - name: Build - run: cargo build --verbose + run: cargo +"$RUST_TOOLCHAIN" build --workspace --verbose - name: Run tests - run: cargo test --verbose + run: cargo +"$RUST_TOOLCHAIN" test --workspace --verbose + + milestone-gate: + name: Honest milestone gate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + components: rustfmt, clippy + + - name: Install system dependencies + run: ./scripts/install-deps.sh - - name: Run integration tests - if: matrix.project == 'cli' && matrix.os == 'ubuntu-latest' - run: tests/all.sh + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} + + - name: Cache cargo index + uses: actions/cache@v4 + with: + path: ~/.cargo/git + key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} + + - name: Cache cargo build + uses: actions/cache@v4 + with: + path: target + key: ${{ runner.os }}-cargo-build-target-gate-${{ hashFiles('Cargo.lock') }} - - name: Run workload replay smoke - if: matrix.project == 'cli' && matrix.os == 'ubuntu-latest' + - name: Allow unprivileged user namespaces run: | - cat > /tmp/agentfs-replay-smoke.jsonl <<'EOF' - {"op":"mkdir","path":"/app"} - {"op":"write_file","path":"/app/hello.txt","content":"hello"} - {"op":"read_file","path":"/app/hello.txt"} - {"op":"stat","path":"/app/hello.txt"} - EOF - ../scripts/validation/replay/replay_workload.py --agentfs-bin target/debug/agentfs /tmp/agentfs-replay-smoke.jsonl + # Ubuntu 24.04 runner images gate unprivileged user namespaces behind + # AppArmor; without this the agentfs run suites SKIP and the strict + # gate goes red instead of exercising the sandbox for real. + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Build pjdfstest - if: matrix.project == 'cli' && matrix.os == 'ubuntu-latest' run: | sudo apt-get update sudo apt-get install -y autoconf automake make gcc perl @@ -91,61 +111,29 @@ jobs: ./configure make pjdfstest - - name: Check pjdfstest harness - if: matrix.project == 'cli' && matrix.os == 'ubuntu-latest' + - name: Run shell and Python gate + env: + # The runner kernel ships FUSE-over-io_uring disabled + # (/sys/module/fuse/parameters/enable_uring reads N), so the kernel + # refuses ring registration and the panic-census uring leg can never + # run here. That one SKIP is allowlisted; every other SKIP still + # fails the strict gate. The corruption-torture uring leg needs no + # entry: with enable_uring=N the mount falls back to the legacy + # channel and the leg passes. + AGENTFS_GATE_ALLOWED_SKIPS: fuse-sigint-panic-census run: | - set +e - ../scripts/validation/posix/run-pjdfstest.sh \ - --agentfs-bin target/debug/agentfs \ - --pjdfstest-dir /tmp/pjdfstest \ - --profile phase45-ci - status=$? - set -e - if [ "$status" -ne 0 ] && [ "$status" -ne 77 ]; then - exit "$status" - fi - - check: - name: Check (${{ matrix.os }}, ${{ matrix.project }}) - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - project: [cli, sdk/rust] - defaults: - run: - working-directory: ${{ matrix.project }} - steps: - - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly - - - name: Install system dependencies - if: matrix.project == 'cli' && (matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest') - run: ../scripts/install-deps.sh - - - name: Check - run: cargo check --all-features + # scripts/gate.sh runs crates/agentfs-cli/tests/all.sh with + # AGENTFS_GATE_STRICT=1, then phase8-validation.py --smoke (which + # includes the noopen/flush/base-drift coherence harnesses) and the + # consistency-canon census. + scripts/gate.sh - check-no-default-features: - name: Check no-default-features (cli) - runs-on: ubuntu-latest - defaults: - run: - working-directory: cli - steps: - - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly - - - name: Check with no default features - run: cargo check --no-default-features + - name: Run pjdfstest phase5-ci + run: | + scripts/validation/posix/run-pjdfstest.sh \ + --agentfs-bin "$PWD/target/release/agentfs" \ + --pjdfstest-dir /tmp/pjdfstest \ + --profile phase5-ci build-linux-arm64: name: Build (Linux arm64) @@ -153,39 +141,34 @@ jobs: steps: - uses: actions/checkout@v4 - # necessary for sandbox - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y libunwind-dev + run: ./scripts/install-deps.sh - name: Install Rust toolchain uses: dtolnay/rust-toolchain@master with: - toolchain: nightly + toolchain: ${{ env.RUST_TOOLCHAIN }} - name: Cache cargo registry uses: actions/cache@v4 with: path: ~/.cargo/registry - key: linux-arm64-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} - name: Cache cargo index uses: actions/cache@v4 with: path: ~/.cargo/git - key: linux-arm64-cargo-index-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} - name: Cache cargo build uses: actions/cache@v4 with: - path: cli/target - key: linux-arm64-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + path: target + key: ${{ runner.os }}-cargo-build-target-arm64-${{ hashFiles('Cargo.lock') }} - - name: Build CLI - working-directory: cli - run: cargo build --verbose + - name: Build workspace + run: cargo +"$RUST_TOOLCHAIN" build --workspace --verbose - name: Run tests - working-directory: cli - run: cargo test --verbose + run: cargo +"$RUST_TOOLCHAIN" test --workspace --verbose diff --git a/.github/workflows/typescript.yml b/.github/workflows/typescript.yml deleted file mode 100644 index 680ba80a..00000000 --- a/.github/workflows/typescript.yml +++ /dev/null @@ -1,135 +0,0 @@ -name: TypeScript CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - test: - name: Test - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest] - node-version: [20, 22] - defaults: - run: - working-directory: sdk/typescript - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - cache-dependency-path: sdk/typescript/package-lock.json - - - name: Install dependencies - run: npm ci - - - name: Build - run: npm run build - - - name: Test - run: npm test - - test-browser: - name: Test - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - node-version: [22] - defaults: - run: - working-directory: sdk/typescript - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - cache-dependency-path: sdk/typescript/package-lock.json - - - name: Install dependencies - run: npm ci - - - name: Build - run: npm run build - - - name: Install playwright - run: npx playwright install - - - name: Test - run: npm run test:browser - - check: - name: Check - runs-on: ubuntu-latest - defaults: - run: - working-directory: sdk/typescript - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'npm' - cache-dependency-path: sdk/typescript/package-lock.json - - - name: Install dependencies - run: npm ci - - examples: - name: Test Examples - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Build SDK - working-directory: sdk/typescript - run: | - npm ci - npm run build - - - name: Test Mastra Example - working-directory: examples/mastra/research-assistant - run: | - npm ci - npm run build - - - name: Test Claude Agent SDK Example - working-directory: examples/claude-agent/research-assistant - run: | - npm ci - npm run build - - - name: Test OpenAI Agents Example - working-directory: examples/openai-agents/research-assistant - run: | - npm ci - npm run build - - - name: Test AI SDK Example - working-directory: examples/ai-sdk-just-bash - run: | - npm ci - npm run typecheck - - - name: Test Cloudflare Example - working-directory: examples/cloudflare - run: | - npm ci - npm run typecheck diff --git a/.gitignore b/.gitignore index 17167cc0..aafaccef 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,8 @@ __MACOSX/ .AppleDouble .LSOverride -Icon[ ] +Icon[ +] # Thumbnails ._* @@ -26,9 +27,11 @@ Temporary Items .apdisk .agentfs +target/ # Python bytecode cache __pycache__/ # Large benchmark fixtures - regenerate via 'git clone --bare openai/codex' .agents/benchmarks/fixtures/ +.agents/missions/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c7f87b1..1ed085b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,83 @@ # Changelog +## [Unreleased] - Fork-era: VFS Right-Thing Restructure + +This fork restructured the post-0.6.4 tree around a five-crate Rust +workspace. The changes below are the user-visible summary of that campaign; +behavior-preserving moves are not listed individually. + +### Removed + +- Deleted SDKs: the Go, Python, and TypeScript SDKs and their CI workflows; + the Rust library survives as the `agentfs-core` crate. +- The standalone example projects (built against the deleted SDKs). +- The experimental ptrace sandbox and the `--experimental-sandbox` flag; + `agentfs run` is FUSE+overlay in Linux user/mount namespaces (NFS + + Sandbox on macOS). +- Windows stubs and the Windows dist target; supported platforms are Linux + (first-tier) and macOS (second-tier: NFS mount plus a sandboxed + `agentfs run`). +- The `abi-7-*` FUSE feature matrix (17 features collapsed into the one + compiled ABI level) and the dead vendored fuser/nfsserve surface. +- The legacy path-based SDK API and the `AGENTFS_OVERLAY_PARTIAL_ORIGIN` + env opt-in (superseded by the first-class `--partial-origin` policy). +- `migrate-v0-5`: one `agentfs migrate` now lands any supported old schema + (v0.0, v0.2, v0.4) at the current version, in place by default or with + `--copy`-based re-chunking. + +### Changed + +- One root workspace after the crate split, five crates in a clean DAG — + `agentfs-core` (storage engine, overlay, schema authority, typed config, + telemetry, semantics), `agentfs-fuse` and `agentfs-nfs` (sealed transport + + adapter crates), `agentfs-mount` (one mount/supervision lifecycle), and + `agentfs-cli` (thin edge with a single error reporter). +- Config: every runtime knob is a typed declaration parsed at the crate + edge with one truthy grammar; the generated `docs/KNOBS.md` ledger is + parity-checked in CI, as is the `docs/MANUAL.md` command reference + (generated from clap). User docs moved under `docs/`. +- Telemetry: one macro registry and a single report sink replace the + hand-rolled six-way counter boilerplate. +- Semantics: one `Semantics` layer under both adapters — a single + permission implementation, explicit ack durability on every write path + (NFS WRITE acks FILE_SYNC only after commit), and one handle/lifecycle + authority. + +### Fixed + +- macOS `agentfs run` left reads unscoped (a blanket `(allow file-read*)` + in the generated Seatbelt profile) while Linux hid home and temp dirs + behind namespaces. The profile is now default-deny for reads: only the + session paths, the allowed directories (defaults plus `--allow`), and a + curated set of platform read roots stay readable; write scoping is + unchanged. Pinned by macOS-gated unit tests; runtime behavior is covered + by a new read-scoping leg in the manual macOS release gate + (`scripts/validation/macos-nfs-git-validation.sh`). +- FUSE teardown deadlocks on both transport legs (classic and io_uring). +- NFS durability lie (FILE_SYNC acked without fsync) and non-graceful + server shutdown. +- Overlay base-directory rename silently emptying the source: now `EXDEV`. +- Stale-overlay reads after external base mutation are rejected. +- Schema `ALTER`s no longer swallow errors blanket-`.ok()`-style. +- Mounts racing the kernel-side drain of a just-closed FUSE-over-io_uring + connection are bounded (retry, then a clear error) instead of wedging + inside `mount(2)` forever (kernel constraint, see docs/MANUAL.md). +- `tursodb-ephemeral-*` sort-spill litter from the turso_core 0.5.3 + dependency (never unlinked upstream, `vdbe/execute.rs:10096`): the CLI + now scopes `TMPDIR` to a per-process spill dir cleaned on exit, without + leaking the override into `run`/`exec` children. Track the upstream + unlink fix before removing this workaround. + +### Validation + +- Honest CI gate (`scripts/gate.sh`): strict shell suite where SKIP is + red, corruption torture on both uring legs, Phase 8 smoke, and the + no-open/no-flush coherence gates. +- Local-only perf gate: serialized median-of-5 codex workload benchmark + against a pinned baseline; any per-phase median regression >5% is red. +- The macOS NFS git validation script is documented as a manual release + gate to run on real hardware. + ## [0.6.4] - 2026-03-25 ### Fixed diff --git a/sdk/rust/Cargo.lock b/Cargo.lock similarity index 82% rename from sdk/rust/Cargo.lock rename to Cargo.lock index b8744d7a..ae3fcbc3 100644 --- a/sdk/rust/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "aegis" -version = "0.9.8" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78412fa53e6da95324e8902c3641b3ff32ab45258582ea997eb9169c68ffa219" +checksum = "e07d39d15384924b35b70d7b8fa1f9a2934101dd3fa4722ede163cc4f9b7b960" dependencies = [ "cc", "softaes", @@ -48,7 +48,33 @@ dependencies = [ ] [[package]] -name = "agentfs-sdk" +name = "agentfs-cli" +version = "0.6.4" +dependencies = [ + "agentfs-core", + "agentfs-mount", + "anyhow", + "base64", + "chrono", + "clap", + "clap_complete", + "dirs", + "hex", + "libc", + "parking_lot", + "serde", + "serde_json", + "sha1", + "tempfile", + "tokio", + "tracing", + "tracing-subscriber", + "turso", + "uuid", +] + +[[package]] +name = "agentfs-core" version = "0.6.4" dependencies = [ "aegis", @@ -58,9 +84,10 @@ dependencies = [ "lru", "parking_lot", "proptest", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_json", + "smallvec", "tempfile", "thiserror 1.0.69", "tokio", @@ -68,6 +95,62 @@ dependencies = [ "turso", ] +[[package]] +name = "agentfs-fuse" +version = "0.6.4" +dependencies = [ + "agentfs-core", + "anyhow", + "async-trait", + "libc", + "memchr", + "nix", + "page_size", + "parking_lot", + "smallvec", + "tempfile", + "tokio", + "tracing", + "turso", + "zerocopy", +] + +[[package]] +name = "agentfs-mount" +version = "0.6.4" +dependencies = [ + "agentfs-core", + "agentfs-fuse", + "agentfs-nfs", + "anyhow", + "async-trait", + "libc", + "tempfile", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "agentfs-nfs" +version = "0.6.4" +dependencies = [ + "agentfs-core", + "anyhow", + "async-trait", + "byteorder", + "filetime", + "libc", + "num-derive", + "num-traits", + "smallvec", + "tempfile", + "tokio", + "tokio-util", + "tracing", + "uuid", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -98,23 +181,67 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "antithesis_sdk" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dbd97a5b6c21cc9176891cf715f7f0c273caf3959897f43b9bd1231939e675" +checksum = "08410fcac93669a476c006cd6c4512ac1e2b30fd117231a5d55d8a2c76599b82" dependencies = [ "libc", "libloading", "linkme", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "rustc_version_runtime", "serde", "serde_json", @@ -122,15 +249,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "arc-swap" -version = "1.9.0" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] @@ -160,9 +287,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" @@ -201,7 +328,7 @@ dependencies = [ "quote", "regex", "rustc-hash 1.1.0", - "shlex", + "shlex 1.3.0", "syn", "which", ] @@ -223,15 +350,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -239,6 +366,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "branches" version = "0.4.4" @@ -259,9 +395,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" @@ -291,9 +427,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "cast" @@ -303,12 +439,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.57" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -326,6 +462,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "cfg_block" version = "0.1.1" @@ -334,13 +476,14 @@ checksum = "18758054972164c3264f7c8386f5fc6da6114cb46b619fd365d4e3b2dc3ae487" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -395,11 +538,12 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -408,16 +552,54 @@ version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ + "anstream", "anstyle", - "clap_lex", + "clap_lex 1.1.0", + "strsim", +] + +[[package]] +name = "clap_complete" +version = "4.5.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39615915e2ece2550c0149addac32fb5bd312c657f43845bb9088cb9c8a7c992" +dependencies = [ + "clap", + "clap_lex 0.7.7", + "is_executable", + "shlex 1.3.0", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + [[package]] name = "clap_lex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -574,30 +756,59 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "powerfmt", + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", ] [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "env_filter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", + "regex", ] [[package]] name = "env_logger" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "env_filter", "log", @@ -633,15 +844,25 @@ checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" dependencies = [ "getrandom 0.3.4", "libm", - "rand 0.9.2", + "rand 0.9.4", "siphasher", ] [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] [[package]] name = "find-msvc-tools" @@ -740,6 +961,7 @@ dependencies = [ "futures-sink", "futures-task", "pin-project-lite", + "slab", ] [[package]] @@ -759,9 +981,9 @@ checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc" [[package]] name = "generator" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" dependencies = [ "cc", "cfg-if", @@ -807,15 +1029,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3", ] [[package]] @@ -856,12 +1076,6 @@ dependencies = [ "foldhash", ] -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - [[package]] name = "heck" version = "0.5.0" @@ -891,9 +1105,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -930,9 +1144,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.8.1" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", @@ -943,7 +1157,6 @@ dependencies = [ "httparse", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1009,24 +1222,6 @@ dependencies = [ "cc", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - [[package]] name = "inout" version = "0.1.4" @@ -1047,9 +1242,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.11" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" +checksum = "9080b15e63775b9a2ac7dca720f7050a8b955e092ea0f6020a4a80f69998cdc0" dependencies = [ "bitflags", "cfg-if", @@ -1067,6 +1262,21 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "is_executable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -1102,11 +1312,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] @@ -1122,17 +1333,11 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" -version = "0.2.183" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -1152,11 +1357,19 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libmimalloc-sys" -version = "0.1.44" +version = "0.1.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" +checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" dependencies = [ "cc", +] + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ "libc", ] @@ -1203,9 +1416,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "loom" @@ -1226,7 +1439,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.5", + "hashbrown", ] [[package]] @@ -1240,9 +1453,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memoffset" @@ -1277,9 +1490,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.48" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" +checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" dependencies = [ "libmimalloc-sys", ] @@ -1292,9 +1505,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.1.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", @@ -1318,6 +1531,18 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1339,9 +1564,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6" dependencies = [ "num-integer", "num-traits", @@ -1349,9 +1574,20 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "num-integer" @@ -1377,6 +1613,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "oorandom" version = "11.1.5" @@ -1391,15 +1633,14 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -1423,9 +1664,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -1433,6 +1674,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "owo-colors" version = "3.5.0" @@ -1441,13 +1688,23 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "pack1" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6e7cd9bd638dc2c831519a0caa1c006cab771a92b1303403a8322773c5b72d6" +checksum = "e3b7bb0ecf2e447b1f20ee94ee79ef6eed1e9d4b3c36ce1903b9dea3bf205523" dependencies = [ "bytemuck", ] +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1483,17 +1740,11 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plotters" @@ -1593,7 +1844,7 @@ dependencies = [ "bit-vec", "bitflags", "num-traits", - "rand 0.9.2", + "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -1604,9 +1855,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", "prost-derive", @@ -1614,9 +1865,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", "itertools 0.14.0", @@ -1633,9 +1884,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -1660,9 +1911,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -1671,9 +1922,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -1737,18 +1988,18 @@ dependencies = [ [[package]] name = "rapidhash" -version = "4.4.1" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +checksum = "32b266a82f4aa99bb5c25e28d11cc44ace63d91adbcbcee4d323e2ae3d49ef37" dependencies = [ "rustversion", ] [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -1773,11 +2024,22 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -1798,15 +2060,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "roaring" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba9ce64a8f45d7fc86358410bb1a82e8c987504c0d4900e9141d69a9f26c885" +checksum = "1dedc5658c6ecb3bdb5ef5f3295bb9253f42dcf3fd1402c03f6b1f7659c3c4a9" dependencies = [ "bytemuck", "byteorder", @@ -1820,9 +2082,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -1948,9 +2210,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -1984,9 +2246,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -1995,6 +2257,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha1_smol" version = "1.0.1" @@ -2016,6 +2289,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "shuttle" version = "0.8.1" @@ -2028,7 +2307,7 @@ dependencies = [ "generator", "hex", "owo-colors", - "rand 0.8.5", + "rand 0.8.6", "rand_core 0.6.4", "rand_pcg", "scoped-tls", @@ -2057,21 +2336,27 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -2079,9 +2364,15 @@ dependencies = [ [[package]] name = "softaes" -version = "0.1.3" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e14297decde697ddf377c25752aead0927d5cfc89c2684d2af96901a4ceeea" + +[[package]] +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef461faaeb36c340b6c887167a9054a034f6acfc50a014ead26a02b4356b3de" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" @@ -2111,11 +2402,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -2135,7 +2432,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -2192,12 +2489,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -2207,15 +2503,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -2233,9 +2529,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -2250,9 +2546,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -2269,6 +2565,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tower-service" version = "0.3.3" @@ -2288,11 +2597,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", + "symlink", "thiserror 2.0.18", "time", "tracing-subscriber", @@ -2412,12 +2722,12 @@ dependencies = [ "parking_lot", "paste", "polling", - "rand 0.9.2", + "rand 0.9.4", "rapidhash", "regex", "regex-syntax", "roaring", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "rustix 1.1.4", "ryu", "serde_json", @@ -2550,14 +2860,14 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" dependencies = [ - "rand 0.9.2", + "rand 0.9.4", ] [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unarray" @@ -2586,12 +2896,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "universal-hash" version = "0.5.1" @@ -2602,13 +2906,19 @@ dependencies = [ "subtle", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "sha1_smol", "wasm-bindgen", @@ -2668,27 +2978,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -2699,9 +3000,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2709,9 +3010,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -2722,52 +3023,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -2785,6 +3052,22 @@ dependencies = [ "rustix 0.38.44", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -2794,6 +3077,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -2937,91 +3226,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen" -version = "0.51.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "wyz" @@ -3034,18 +3241,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..b822a0b9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,75 @@ +[workspace] +members = [ + "crates/agentfs-cli", + "crates/agentfs-core", + "crates/agentfs-fuse", + "crates/agentfs-nfs", + "crates/agentfs-mount", +] +resolver = "2" + +[workspace.package] +version = "0.6.4" +edition = "2021" +license = "MIT" +repository = "https://github.com/tursodatabase/agentfs" + +# Concurrency canon (architecture.md section 7 item 7): no lock guard held +# across an .await; enforced structurally, not by review. +[workspace.lints.clippy] +await_holding_lock = "deny" + +[workspace.dependencies] +agentfs-core = { path = "crates/agentfs-core" } +agentfs-cli = { path = "crates/agentfs-cli" } +agentfs-fuse = { path = "crates/agentfs-fuse" } +agentfs-mount = { path = "crates/agentfs-mount" } +agentfs-nfs = { path = "crates/agentfs-nfs" } + +anyhow = "1.0" +async-trait = "0.1" +base64 = "0.22" +byteorder = "1.5" +chrono = { version = "0.4.42", features = ["serde"] } +clap = { version = "4", features = ["derive", "env"] } +clap_complete = { version = "=4.5.61", features = ["unstable-dynamic"] } +criterion = { version = "0.5", features = ["async_tokio"] } +dirs = "6" +filetime = "0.2" +hex = "0.4" +libc = "0.2" +lru = "0.12" +memchr = "2.7" +nix = { version = "0.29", features = ["fs", "user"] } +num-derive = "0.4" +num-traits = "0.2" +page_size = "0.6" +parking_lot = "0.12.5" +proptest = "1.4" +rand = "0.8" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.147" +sha1 = "0.10" +smallvec = "1.6" +tempfile = "3.23.0" +thiserror = "1.0" +tokio = { version = "1", features = ["full"] } +tokio-util = "0.7" +tracing = "0.1.44" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +turso = { version = "0.5", features = ["sync"] } +uuid = { version = "1", features = ["v4"] } +zerocopy = { version = "0.8", features = ["derive"] } + +# Ensure transitive `aegis` (pulled in by `turso`) builds on macOS arm64 by +# forcing the `pure-rust` feature, which skips the C/NEON backend that fails +# with Apple clang (`veor3q_u8` in `aegis128l_neon_sha3.c`). +aegis = { version = "0.9.6", features = ["pure-rust"] } + +# The profile that `dist` will build with. +[profile.dist] +inherits = "release" +lto = "thin" + +[profile.bench] +debug = true diff --git a/MANUAL.md b/MANUAL.md deleted file mode 100644 index 527aa003..00000000 --- a/MANUAL.md +++ /dev/null @@ -1,525 +0,0 @@ -# AgentFS Reference Guide - -Command-line reference for the AgentFS CLI. - -For guides, tutorials, and SDK documentation, see [docs.turso.tech/agentfs](https://docs.turso.tech/agentfs). - -## Installation - -```bash -curl -fsSL https://github.com/tursodatabase/agentfs/releases/latest/download/agentfs-installer.sh | sh -``` - -## Commands - -### agentfs init - -Initialize a new agent filesystem. - -``` -agentfs init [OPTIONS] [ID] -``` - -**Arguments:** -- `ID` - Agent identifier (default: `agent-{timestamp}`) - -**Options:** -- `--force` - Overwrite existing agent filesystem -- `--base ` - Base directory for overlay filesystem (copy-on-write) -- `--key ` - Hex-encoded encryption key for local encryption -- `--cipher ` - Cipher algorithm (required with `--key`) -- `--sync-remote-url ` - Remote Turso database URL for sync -- `--sync-partial-prefetch` - Enable prefetching for partial sync -- `--sync-partial-segment-size ` - Segment size for partial sync -- `--sync-partial-bootstrap-query ` - Custom bootstrap query -- `--sync-partial-bootstrap-length ` - Bootstrap prefix length - -**Note:** Local encryption and cloud sync cannot be used together. - -**Options (continued):** -- `-c, --command ` - Command to execute after initialization (see below) -- `--backend ` - Mount backend for `-c` option (`fuse` or `nfs`) - -**Running a command after init:** - -The `-c` option initializes the filesystem, mounts it to a temporary directory, runs the specified command with that directory as the working directory, then automatically unmounts. - -```bash -# Initialize and run a command in the new filesystem -agentfs init my-agent -c "touch hello.txt && ls -la" - -# With overlay filesystem -agentfs init my-overlay --base /path/to/project -c "make build" -``` - -### agentfs exec - -Execute a command with an AgentFS filesystem mounted (Unix only). - -``` -agentfs exec [OPTIONS] [ARGS]... -``` - -Mounts the specified AgentFS to a temporary directory, runs the command with that directory as the working directory, then automatically unmounts. This is useful for running tools that need filesystem access without a persistent mount. - -If the AgentFS was initialized with `--base` (overlay mode), the overlay filesystem is used automatically. - -**Arguments:** -- `ID_OR_PATH` - Agent identifier or database path -- `COMMAND` - Command to execute -- `ARGS` - Arguments for the command - -**Options:** -- `--backend ` - Mount backend (`fuse` on Linux, `nfs` on macOS by default) -- `--key ` - Hex-encoded encryption key for encrypted databases -- `--cipher ` - Cipher algorithm (required with `--key`) - -**Examples:** - -```bash -# Run ls in the AgentFS root -agentfs exec my-agent ls -la - -# Run a build command -agentfs exec my-overlay make build - -# With encryption -agentfs exec my-agent --key $KEY --cipher aes256gcm cat /config.json -``` - -### agentfs run - -Execute a program in a sandboxed environment with copy-on-write filesystem. - -``` -agentfs run [OPTIONS] [ARGS]... -``` - -**Options:** -- `--session ` - Named session for persistence across runs -- `--allow ` - Allow write access to additional directories (repeatable) -- `--no-default-allows` - Disable default allowed directories -- `--key ` - Hex-encoded encryption key for delta layer -- `--cipher ` - Cipher algorithm (required with `--key`) -- `--experimental-sandbox` - Use ptrace-based syscall interception (Linux only) -- `--strace` - Show intercepted syscalls (requires `--experimental-sandbox`) - -**Platform behavior:** - -Linux uses FUSE + overlay filesystem with user namespaces. macOS uses NFS + overlay filesystem with Apple's Sandbox. - -Default allowed directories (macOS): `~/.claude`, `~/.codex`, `~/.config`, `~/.cache`, `~/.local`, `~/.npm`, `/tmp` - -**Linux FUSE performance and cache controls:** - -AgentFS uses a bounded FUSE worker pool on Linux. The pool removes the old -global backend mutex from read paths while preserving copy-on-write isolation: -reads are admitted through a shared read lane, and metadata/content mutations -are admitted through an exclusive write lane before reaching the SQLite-backed -delta. - -| Variable | Default | Description | -|---|---:|---| -| `AGENTFS_FUSE_WORKERS` | `auto` | `serial`, `auto`, an integer worker count, or a percent such as `25%`. Defaults to `auto` (~`AGENTFS_FUSE_CPU_PERCENT`% of host CPUs). Set to `serial` to fall back to single-threaded dispatch. | -| `AGENTFS_FUSE_QUEUE` | derived | Request queue capacity. Accepts an integer or memory percent. | -| `AGENTFS_FUSE_CPU_PERCENT` | `25` | Target CPU fraction when `AGENTFS_FUSE_WORKERS=auto`. | -| `AGENTFS_FUSE_MEMORY_PERCENT` | `25` | Target memory fraction for derived queue sizing. | -| `AGENTFS_FUSE_SYNC_INVAL` | `0` | Opt-in synchronous kernel cache invalidation. Default uses deferred (off-thread) invalidation which is safer under parallel workers: synchronous notifies issued from a request handler can block waiting for inline `FUSE_FORGET` traffic that the session thread cannot deliver while every dispatch lane is busy, so combining `AGENTFS_FUSE_SYNC_INVAL=1` with parallel `AGENTFS_FUSE_WORKERS` can deadlock under git workloads. The kernel cache fast path no longer requires this flag. | -| `AGENTFS_FUSE_ENTRY_TTL_MS` | `1000` | Kernel dentry TTL when the kernel cache fast path is active (parallel workers); otherwise forced to `0`. | -| `AGENTFS_FUSE_ATTR_TTL_MS` | `1000` | Kernel attribute TTL when the kernel cache fast path is active (parallel workers); otherwise forced to `0`. | -| `AGENTFS_FUSE_NEG_TTL_MS` | `1000` | Kernel negative-entry TTL when the kernel cache fast path is active (parallel workers); otherwise forced to `0`. | -| `AGENTFS_FUSE_READDIRPLUS` | `auto` | `off`, `auto`, or `always`; accepted when the kernel cache fast path is active (parallel workers). | -| `AGENTFS_FUSE_WRITEBACK` | `1` | Requests FUSE writeback cache; accepted when the kernel cache fast path is active (parallel workers). | -| `AGENTFS_FUSE_KEEPCACHE` | `1` | Requests `FOPEN_KEEP_CACHE` for eligible read-only base files; accepted when the kernel cache fast path is active (parallel workers). | - -By default (no env vars set), AgentFS runs with parallel FUSE dispatch and -deferred kernel-cache invalidation, which enables the kernel cache fast path: -1 s TTLs on dentries/attrs/negative lookups, writeback cache, `FOPEN_KEEP_CACHE` -on eligible reads, and readdirplus auto. Each mutation path (`create`, `mkdir`, -`mknod`, `symlink`, `link`, `unlink`, `rmdir`, `rename`, `write`, `flush`, -`setattr`) is audited in debug builds to confirm a kernel cache invalidation -(synchronous or deferred) is queued before any success reply. - -Override to `AGENTFS_FUSE_WORKERS=serial` to fall back to the pre-Phase-8 -behavior where the kernel cache fast path is fully disabled (TTLs=0, no -writeback, no keepcache, no readdirplus). Setting `AGENTFS_FUSE_SYNC_INVAL=1` -re-enables synchronous invalidation; use it only with `AGENTFS_FUSE_WORKERS=serial` -to avoid the parallel-dispatch deadlock described above. All copy-on-write -writes remain in the AgentFS database; no sandbox write is applied to the base -filesystem regardless of the cache configuration. - -### agentfs mount - -Mount an agent filesystem or list mounted filesystems. - -``` -agentfs mount [OPTIONS] [ID_OR_PATH] [MOUNT_POINT] -``` - -Without arguments, lists all mounted agentfs filesystems. - -**Options:** -- `-a, --auto-unmount` - Automatically unmount on exit -- `--allow-root` - Allow root user to access filesystem -- `-f, --foreground` - Run in foreground -- `--uid ` - User ID for all files -- `--gid ` - Group ID for all files - -**Unmounting:** -- Linux: `fusermount -u ` -- macOS: `umount ` - -**macOS NFS git validation (#333):** - -To manually validate the macOS NFS path used by git loose-object writes, run the -repository harness on a macOS host: - -```bash -cargo build --manifest-path cli/Cargo.toml --no-default-features -scripts/validation/macos-nfs-git-validation.sh \ - --agentfs-bin "$PWD/cli/target/debug/agentfs" -``` - -The script initializes a temporary AgentFS database, mounts it via -`agentfs mount --backend nfs`, runs `git init`, `git add`, `git commit`, and -`git fsck --strict`, then unmounts and cleans up. A passing run ends with -`macOS NFS git validation passed` and a nonzero loose-object count. On non-macOS -hosts the script exits `77` to report an intentional skip. - -### agentfs serve mcp - -Start an MCP (Model Context Protocol) server. - -``` -agentfs serve mcp [OPTIONS] -``` - -**Options:** -- `--tools ` - Comma-separated list of tools to expose (default: all) - -**Available tools:** - -Filesystem: `read_file`, `write_file`, `readdir`, `mkdir`, `remove`, `rename`, `stat`, `access` - -Key-Value: `kv_get`, `kv_set`, `kv_delete`, `kv_list` - -### agentfs serve nfs - -Start an NFS server to export AgentFS over the network. - -``` -agentfs serve nfs [OPTIONS] -``` - -**Options:** -- `--bind ` - IP address to bind (default: `127.0.0.1`) -- `--port ` - Port to listen on (default: `11111`) - -**Mounting from client:** -```bash -mount -t nfs -o vers=3,tcp,port=11111,mountport=11111,nolock :/ -``` - -### agentfs sync - -Synchronize agent filesystem with a remote Turso database. - -``` -agentfs sync -``` - -**Subcommands:** -- `pull` - Pull remote changes -- `push` - Push local changes -- `stats` - View sync statistics -- `checkpoint` - Create checkpoint - -### agentfs integrity - -Run SQLite and AgentFS schema-invariant checks against a local database. - -``` -agentfs integrity [OPTIONS] -``` - -**Arguments:** -- `ID_OR_PATH` - Agent identifier or database path - -**Options:** -- `--json` - Emit a machine-readable report -- `--key ` - Hex-encoded encryption key for encrypted databases -- `--cipher ` - Cipher algorithm (required with `--key`) - -**Examples:** - -```bash -# Check by agent ID -agentfs integrity my-agent --json - -# Check by database path -agentfs integrity .agentfs/my-agent.db --json -``` - -The command runs `PRAGMA integrity_check`, validates required AgentFS tables and -v0.5 config, checks inline/chunk storage invariants, verifies namespace -references, and checks overlay metadata tables when present. It exits nonzero if -any check fails. - -### agentfs backup - -Create a portable main-database snapshot for a local AgentFS database. - -``` -agentfs backup [OPTIONS] -``` - -**Arguments:** -- `ID_OR_PATH` - Agent identifier or database path -- `TARGET_DB` - New database path to create - -**Options:** -- `--verify` - Reopen the copied main database and run integrity checks -- `--key ` - Hex-encoded encryption key for encrypted databases -- `--cipher ` - Cipher algorithm (required with `--key`) - -**Examples:** - -```bash -# Checkpoint, copy, reopen, and verify a portable backup -agentfs backup my-agent /tmp/my-agent-backup.db --verify - -# Backup using database paths -agentfs backup .agentfs/my-agent.db ./my-agent-backup.db --verify -``` - -The command checkpoints and truncates the source WAL before copying only the -main database file. The target must not already exist. Databases with -partial-origin overlay rows are rejected because their file contents still -depend on the external base tree; keep the base tree with the database or -materialize the overlay before creating a portable backup. - -### agentfs migrate - -Migrate historical database schemas through the legacy v0.4 layout. - -``` -agentfs migrate [OPTIONS] -``` - -Upgrades an AgentFS database schema through the legacy v0.4 layout. v0.5 is a layout-changing schema and uses the copy-based `agentfs migrate-v0-5` command instead of in-place mutation. - -**Arguments:** -- `ID_OR_PATH` - Agent identifier or database path - -**Options:** -- `--dry-run` - Preview migration without applying changes - -**Examples:** - -```bash -# Preview pending migrations -agentfs migrate my-agent --dry-run - -# Apply migrations -agentfs migrate my-agent - -# Migrate using database path -agentfs migrate .agentfs/my-agent.db -``` - -**Output:** - -The command displays the current and target schema versions, then applies any necessary migrations: - -``` -Database: .agentfs/my-agent.db -Current schema version: v0.2 -Target schema version: v0.4 - -Applying migrations... - Migrating v0.2 -> v0.4... - Added atime_nsec column to fs_inode - Added mtime_nsec column to fs_inode - Added ctime_nsec column to fs_inode - Added rdev column to fs_inode - v0.2 -> v0.4 migration complete. - -Migration completed successfully. -``` - -**Notes:** -- Migrations are idempotent and safe to run multiple times -- This command does not convert v0.4 databases to v0.5 -- Always backup your database before running migrations on production data - -### agentfs migrate-v0-5 - -Copy a v0.4 database into a new v0.5 database. - -``` -agentfs migrate-v0-5 [OPTIONS] -``` - -v0.5 changes the file-content layout by defaulting to 64 KiB chunks and storing dense regular files at or below 4 KiB inline in `fs_inode`. Because this is a layout change, migration is copy-only: the source database is opened for verification and copied into a separate target database. - -**Arguments:** -- `SOURCE` - Source v0.4 database path -- `TARGET` - Target v0.5 database path - -**Options:** -- `--verify` - Verify migrated filesystem, KV, tool-call, and overlay state equivalence -- `--overwrite-target` - Replace an existing target database - -**Examples:** - -```bash -# Copy and verify a v0.4 database into v0.5 -agentfs migrate-v0-5 .agentfs/my-agent.db .agentfs/my-agent-v05.db --verify - -# Replace an existing target -agentfs migrate-v0-5 old.db new.db --verify --overwrite-target -``` - -**Notes:** -- The source database is never migrated in place -- Overlay tables (`fs_whiteout`, `fs_origin`, and `fs_overlay_config`) are preserved -- Sparse and large files are streamed during copy/verification rather than materialized whole-file -- Verification includes a checkpointed single-file snapshot check for the target database - -### agentfs fs - -Filesystem operations on agent databases. - -**Common Options:** -- `--key ` - Hex-encoded encryption key for encrypted databases -- `--cipher ` - Cipher algorithm (required with `--key`) - -#### agentfs fs ls - -``` -agentfs fs [OPTIONS] ls [FS_PATH] -``` - -List files and directories. Output: `f ` for files, `d ` for directories. - -#### agentfs fs cat - -``` -agentfs fs [OPTIONS] cat -``` - -Display file contents. - -#### agentfs fs write - -``` -agentfs fs [OPTIONS] write -``` - -Write content to a file. - -### agentfs diff - -Show filesystem changes in overlay mode. - -``` -agentfs diff -``` - -### agentfs timeline - -Display agent action timeline from the tool call audit log. - -``` -agentfs timeline [OPTIONS] -``` - -**Options:** -- `--limit ` - Limit entries (default: 100) -- `--filter ` - Filter by tool name -- `--status ` - Filter by status: `pending`, `success`, `error` -- `--format ` - Output format: `table`, `json` (default: table) - -### agentfs completions - -Manage shell completions. - -``` -agentfs completions install [SHELL] -agentfs completions uninstall [SHELL] -agentfs completions show -``` - -Supported shells: `bash`, `zsh`, `fish`, `powershell` - -## Environment Variables - -**Configuration variables:** - -| Variable | Description | -|----------|-------------| -| `AGENTFS_KEY` | Default encryption key (hex-encoded) | -| `AGENTFS_CIPHER` | Default cipher algorithm | -| `TURSO_DB_AUTH_TOKEN` | Authentication token for cloud sync | - -**Variables set inside the sandbox:** - -| Variable | Description | -|----------|-------------| -| `AGENTFS` | Set to `1` inside AgentFS sandbox | -| `AGENTFS_SANDBOX` | Sandbox type: `macos-sandbox` or `linux-namespace` | -| `AGENTFS_SESSION` | Current session ID | - -## Local Encryption - -AgentFS supports encrypting the local SQLite database at rest using libSQL's encryption feature. - -**Supported ciphers:** -- `aes256gcm` - AES-256-GCM (requires 64-character hex key) -- `aes128gcm` - AES-128-GCM (requires 32-character hex key) -- `aegis256` - AEGIS-256 (requires 64-character hex key) -- `aegis128l` - AEGIS-128L (requires 32-character hex key) -- `aegis128x2`, `aegis128x4`, `aegis256x2`, `aegis256x4` - AEGIS variants - -**Example: Create an encrypted filesystem** - -```bash -# Generate a 256-bit key (64 hex characters) -KEY=$(openssl rand -hex 32) - -# Initialize with encryption -agentfs init --key $KEY --cipher aes256gcm my-secure-agent - -# Access the filesystem -agentfs fs my-secure-agent --key $KEY --cipher aes256gcm ls / -``` - -**Example: Encrypted sandbox session** - -```bash -agentfs run --key $KEY --cipher aes256gcm -- bash -``` - -**Using environment variables:** - -```bash -export AGENTFS_KEY=$(openssl rand -hex 32) -export AGENTFS_CIPHER=aes256gcm - -agentfs init my-secure-agent -agentfs fs my-secure-agent ls / -``` - -**Limitations:** -- Local encryption cannot be used with cloud sync (`--sync-remote-url`) - -## Files - -- `.agentfs/.db` - Agent filesystem database -- `~/.config/agentfs/` - Configuration directory - -## See Also - -- [AgentFS Documentation](https://docs.turso.tech/agentfs) - Guides, tutorials, SDK docs -- [AgentFS Specification](SPEC.md) - SQLite schema specification -- [GitHub Repository](https://github.com/tursodatabase/agentfs) - Source code and examples diff --git a/README.md b/README.md index 62bc30d7..a8cf8b48 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,55 @@ -

-

AgentFS

-

- -

- The filesystem for agents. -

- -

- - Crate - NPM - PyPI - -

-

- Chat with other users of Turso (and Turso Cloud) on Discord -

- ---- +# AgentFS -> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups. +The filesystem for agents: a SQLite-backed virtual filesystem with +copy-on-write sandboxing, mountable over FUSE (Linux) or NFS. -## 🎯 What is AgentFS? +> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups. -AgentFS is a filesystem explicitly designed for AI agents. Just as traditional filesystems provide file and directory abstractions for applications, AgentFS provides the storage abstractions that AI agents need. +## What is AgentFS? -The AgentFS repository consists of the following: +AgentFS stores everything an agent does — every file it creates, every piece +of key-value state, every tool call it records — in a single SQLite database +file. That gives agent state three properties ordinary filesystems don't +have: -* **SDK** - [TypeScript](sdk/typescript), [Python](sdk/python), and [Rust](sdk/rust) libraries for programmatic filesystem access. -* **[CLI](MANUAL.md)** - Command-line interface for managing agent filesystems: - - Mount AgentFS on host filesystem with FUSE on Linux and NFS on macOS. - - Access AgentFS files with a command line tool. -* **[AgentFS Specification](SPEC.md)** - SQLite-based agent filesystem specification. +* **Auditability**: every file operation and tool call is queryable with SQL. +* **Reproducibility**: snapshot an agent's state by copying one file; restore + it to reproduce exact execution states or roll back mistakes. +* **Portability**: the entire agent runtime (files, state, history) moves + between machines as a single `.db` file. -## 💡 Why AgentFS? +For sandboxed coding-agent workloads, AgentFS layers that database over a +read-only view of the host filesystem: reads are scoped to the configured +base tree, writes go only to the AgentFS delta database, and the real +filesystem is never modified. This holds even against prompt-injected agents: +the isolation is enforced at the filesystem layer, not by convention. -AgentFS provides the following benefits for agent state management: +## Repository layout -* **Auditability**: Every file operation, tool call, and state change is recorded in a SQLite database file. Query your agent's complete history with SQL to debug issues, analyze behavior, or meet compliance requirements. -* **Reproducibility**: Snapshot an agent's state at any point with cp agent.db snapshot.db. Restore it later to reproduce exact execution states, test what-if scenarios, or roll back mistakes. -* **Portability**: The entire agent runtime—files, state, history —is stored in a single SQLite file. Move it between machines, check it into version control, or deploy it to any system where Turso runs. +This repository is one Cargo workspace with five crates: -Read more about the motivation for AgentFS in the announcement [blog post](https://turso.tech/blog/agentfs). +| Crate | Role | +|---|---| +| `crates/agentfs-core` | The engine: storage, overlay/copy-on-write, schema authority, typed config, telemetry, semantics (access/durability/handles). The only crate meant for external consumption. | +| `crates/agentfs-fuse` | Sealed Linux FUSE mount surface (transport + adapter). | +| `crates/agentfs-nfs` | Sealed NFSv3 serve surface (transport + adapter). | +| `crates/agentfs-mount` | One mount lifecycle: `mount_fs`, `MountHandle`, supervision, daemonize. | +| `crates/agentfs-cli` | The `agentfs` binary: thin CLI edge over the crates above. | -## 🧑‍💻 Getting Started +Platform support: **Linux is first-tier** (FUSE and NFS backends, `agentfs +run` sandbox, full validation gate). **macOS is second-tier**: NFS mount +plus a sandboxed `agentfs run` (Seatbelt/`sandbox-exec` with default-deny +read scoping), validated by a manual release gate +(`scripts/validation/macos-nfs-git-validation.sh`) run on real hardware — +see [docs/TESTING.md](docs/TESTING.md). No other platforms are supported. -### Using the CLI +## Getting started -Install the AgentFS CLI: +Build and install the CLI from source: ```bash -curl -fsSL https://agentfs.ai/install | bash +cargo +nightly build --release --workspace --bins +install -m 0755 target/release/agentfs ~/.local/bin/ ``` Initialize an agent filesystem: @@ -61,25 +60,21 @@ Created agent filesystem: .agentfs/my-agent.db Agent ID: my-agent ``` -Inspect the agent filesystem: +Inspect it without mounting: ```bash $ agentfs fs my-agent ls -Using agent: my-agent f hello.txt $ agentfs fs my-agent cat hello.txt hello from agent -``` - -You can also use a database path directly: -```bash +# Or address the database file directly $ agentfs fs .agentfs/my-agent.db cat hello.txt hello from agent ``` -View the agent's action timeline: +View the agent's tool-call timeline: ```bash $ agentfs timeline my-agent @@ -90,7 +85,7 @@ ID TOOL STATUS DURATION STARTED 1 web_search success 1200ms 2024-01-05 09:43:45 ``` -You can mount an agent filesystem using FUSE (Linux) or NFS (macOS): +Mount it as a real filesystem (FUSE on Linux, NFS on macOS): ```bash $ agentfs mount my-agent ./mnt @@ -99,124 +94,84 @@ $ cat ./mnt/hello.txt hello ``` -You can also run a program in an experimental sandbox with the agent filesystem mounted at `/agent`: +Run a program in a copy-on-write sandbox over your current directory +(FUSE + user/mount namespaces on Linux): ```bash -$ agentfs run /bin/bash -Welcome to AgentFS! - -$ echo "hello from agent" > /agent/hello.txt -$ cat /agent/hello.txt -hello from agent +$ agentfs run --session my-session -- bash +# ... work normally; every write lands in the delta database, +# the host filesystem is untouched ... $ exit -``` - -Read the **[User Manual](MANUAL.md)** for complete documentation. - -### Using the SDK - -Install the SDK in your project: - -```bash -npm install agentfs-sdk -``` - -Use it in your agent code: - -```typescript -import { AgentFS } from 'agentfs-sdk'; -// Persistent storage with identifier -const agent = await AgentFS.open({ id: 'my-agent' }); -// Creates: .agentfs/my-agent.db - -// Or use ephemeral in-memory database -const ephemeralAgent = await AgentFS.open(); - -// Key-value operations -await agent.kv.set('user:preferences', { theme: 'dark' }); -const prefs = await agent.kv.get('user:preferences'); - -// Filesystem operations -await agent.fs.writeFile('/output/report.pdf', pdfBuffer); -const files = await agent.fs.readdir('/output'); - -// Tool call tracking -await agent.tools.record( - 'web_search', - Date.now() / 1000, - Date.now() / 1000 + 1.5, - { query: 'AI' }, - { results: [...] } -); +# List live sandbox sessions; inspect what a session changed +$ agentfs ps +$ agentfs diff my-session ``` -### Examples - -This source repository also contains examples that demonstrate how to integrate AgentFS with some popular AI frameworks: - -- **[Mastra](examples/mastra/research-assistant)** - Research assistant using the Mastra AI framework -- **[Claude Agent SDK](examples/claude-agent/research-assistant)** - Research assistant using Anthropic's Claude Agent SDK -- **[OpenAI Agents](examples/openai-agents/research-assistant)** - Research assistant using OpenAI Agents SDK -- **[Firecracker](examples/firecracker)** - Minimal Firecracker VM with AgentFS mounted via NFSv3 -- **[AI SDK + just-bash](examples/ai-sdk-just-bash)** - Interactive AI agent using Vercel AI SDK with just-bash for command execution -- **[Cloudflare Workers](examples/cloudflare)** - AI agent using AI SDK + just-bash on Cloudflare Workers with Durable Objects storage - -See the **[examples](examples)** directory for more details. - -## 🔧 How AgentFS Works? - - - -AgentFS is an agent filesystem accessible through an SDK that provides three essential interfaces for agent state management: - -* **Filesystem:** A POSIX-like filesystem for files and directories -* **Key-Value:** A key-value store for agent state and context -* **Toolcall:** A toolcall audit trail for debugging and analysis - -At the heart of AgentFS is the [agent filesystem](SPEC.md), a complete SQLite-based storage system for agents implemented using [Turso](https://github.com/tursodatabase/turso). Everything an agent does—every file it creates, every piece of state it stores, every tool it invokes—lives in a single SQLite database file. - -For sandboxed coding-agent workloads, AgentFS can layer that SQLite-backed -filesystem over a read-only host directory. Reads are scoped to the configured -base tree, while writes go only to the AgentFS delta database. The real -filesystem is never modified by copy-on-write operations. On Linux, the FUSE -backend dispatches requests through a bounded worker pool and a read/write lane: -read-heavy operations can run concurrently against internally synchronized -backends, while namespace and data mutations remain serialized at the -filesystem/SQLite transaction boundaries. This preserves AgentFS's two core -safety properties: one portable database contains the virtual filesystem state, -and sandboxed writes do not touch the real filesystem. - -## 🤔 FAQ +Beyond these basics, the CLI covers the rest of the lifecycle: + +* `agentfs exec` — run a one-shot command over a temporary mount, then + unmount automatically. +* `agentfs clone` — bulk-ingest a git repository straight into the database. +* `agentfs sync` — push/pull the database to a remote Turso (libSQL) server. +* `agentfs serve nfs` / `agentfs serve mcp` — export the filesystem over + NFS, or expose filesystem and KV-store tools to agents over MCP. +* `--key` / `--cipher` — local at-rest encryption of the database. +* `agentfs backup`, `integrity`, `migrate`, `materialize`, `prune` — + portable backups, corruption checks, schema migration, partial-origin + materialization, and mount cleanup. + +The **[User Manual](docs/MANUAL.md)** documents every command; its command +reference is generated from the CLI's own argument definitions, so it cannot +drift from `agentfs --help`. + +## Using AgentFS as a library + +The `agentfs-core` crate provides programmatic access to the same engine the +CLI uses: filesystem, key-value store, and tool-call audit trail over a +single database. See the crate rustdoc (`cargo doc -p agentfs-core`). + +## How it works + +At the heart of AgentFS is the [agent filesystem](docs/SPEC.md), a complete +SQLite-based storage system implemented on +[Turso](https://github.com/tursodatabase/turso). The database schema +separates namespace (dentries) from data (inodes + chunked/inline content), +which enables hard links, POSIX metadata, sparse files, and SQL-queryable +history. + +On Linux, the FUSE backend dispatches requests through a bounded worker pool +with a read/write lane split, kernel-cache acceleration (entry/attr TTLs, +writeback cache, readdirplus), zero-message opens, and an optional +FUSE-over-io_uring transport. All acceleration structures are reconstructible +from the database; the two safety properties — one portable database holds +all virtual filesystem state, and sandboxed writes never reach the host +filesystem — hold regardless of cache configuration. Runtime tunables are +declared in the generated [docs/KNOBS.md](docs/KNOBS.md) ledger. + +## FAQ ### How is AgentFS different from _X_? -[Bubblewrap](https://github.com/containers/bubblewrap) provides filesystem isolation using Linux namespaces and overlays. While you could achieve similar isolation with a `bwrap` call that mounts `/` read-only and uses `--tmp-overlay` on the working directory, the key difference is persistence and queryability: with AgentFS, the upper filesystem is stored in a single SQLite database file, which you can query, snapshot, and move to another machine. Read more about the motivation in the announcement [blog post](https://turso.tech/blog/agentfs). +[Bubblewrap](https://github.com/containers/bubblewrap) provides filesystem isolation using Linux namespaces and overlays. While you could achieve similar isolation with a `bwrap` call that mounts `/` read-only and uses `--tmp-overlay` on the working directory, the key difference is persistence and queryability: with AgentFS, the upper filesystem is stored in a single SQLite database file, which you can query, snapshot, and move to another machine. [Docker Sandbox](https://www.docker.com/blog/docker-sandboxes-a-new-approach-for-coding-agent-safety/) and AgentFS are complementary rather than competing. AgentFS answers "what happened and what's the state?" while Docker Sandboxes answer "how do I run this safely?" You could use both together: run an agent inside a Docker Sandbox for security, while using AgentFS inside that sandbox for structured state management and audit trails. -[Git worktrees](https://git-scm.com/docs/git-worktree) let you check out multiple branches of a repository into separate directories, allowing agents to work on independent copies of the source code—similar to AgentFS. But AgentFS solves the problem at a lower level. With git worktrees, nothing prevents an agent from modifying files outside its worktree: another agent's worktree, system files, or anything else on the filesystem. The isolation is purely conventional, not enforced. AgentFS provides filesystem-level copy-on-write isolation that's system-wide and cannot be bypassed—letting you safely run untrusted agents. And because it operates below git, it also handles untracked files, making it useful beyond just version-controlled source code. +[Git worktrees](https://git-scm.com/docs/git-worktree) let you check out multiple branches of a repository into separate directories, allowing agents to work on independent copies of the source code — similar to AgentFS. But AgentFS solves the problem at a lower level. With git worktrees, nothing prevents an agent from modifying files outside its worktree: another agent's worktree, system files, or anything else on the filesystem. The isolation is purely conventional, not enforced. AgentFS provides filesystem-level copy-on-write isolation that cannot be bypassed, and because it operates below git, it also handles untracked files. ### Why implement AgentFS at the filesystem layer instead of using containers or VMs? -The filesystem layer gives us capabilities that block devices can't. First, because everything is stored in structured SQLite tables, you can query the filesystem, which is essential for auditability and debugging agent behavior. Second, SQLite's write-ahead log enables snapshotting and time-travel forking by capturing every filesystem change. Third, we can provide an SDK that works in environments such as serverless or the browser, where there's no way to mount a block device at all. Note that this approach works fine with containers and VMs too—you can use AgentFS via remote filesystem protocols like NFS or through mechanisms like virtio-fuse. - -## 📚 Learn More - -- **[User Manual](MANUAL.md)** - Complete guide to using the AgentFS CLI and SDK -- **[Agent Filesystem Specification](SPEC.md)** - Technical specification of the agent filesystem SQLite schema -- **[SDK Examples](examples/)** - Working code examples using AgentFS -- **[Turso database](https://github.com/tursodatabase/turso)** - an in-process SQL database, compatible with SQLite. +The filesystem layer gives us capabilities that block devices can't. First, because everything is stored in structured SQLite tables, you can query the filesystem, which is essential for auditability and debugging agent behavior. Second, SQLite's write-ahead log enables snapshotting and time-travel forking by capturing every filesystem change. Third, the engine works in environments where mounting a block device is impossible. This approach also composes with containers and VMs: AgentFS is reachable over remote filesystem protocols like NFS or through mechanisms like virtio-fuse. -### Blog Posts +## Learn more -- **[Introducing AgentFS](https://turso.tech/blog/agentfs)** - The motivation behind AgentFS -- **[AgentFS with FUSE](https://turso.tech/blog/agentfs-fuse)** - Mounting agent filesystems using FUSE -- **[AgentFS with Overlay Filesystem](https://turso.tech/blog/agentfs-overlay)** - Sandboxing agents with copy-on-write overlays -- **[AI Agents with Just Bash](https://turso.tech/blog/agentfs-just-bash)** - Safe bash command execution for agents -- **[AgentFS in the Browser](https://turso.tech/blog/agentfs_browser)** - Running AgentFS in browsers with WebAssembly -- **[Making Coding Agents Safe Using LlamaIndex](https://www.llamaindex.ai/blog/making-coding-agents-safe-using-llamaindex)** - Using AgentFS with LlamaIndex +- **[User Manual](docs/MANUAL.md)** - complete CLI reference (generation-checked against the binary) +- **[Agent Filesystem Specification](docs/SPEC.md)** - the SQLite schema and runtime invariants +- **[Runtime Knobs](docs/KNOBS.md)** - generated ledger of every tunable +- **[Testing](docs/TESTING.md)** - validation gates, benchmark policy, and the manual macOS release gate +- **[CHANGELOG](CHANGELOG.md)** - including the fork-era restructure summary +- **[Turso database](https://github.com/tursodatabase/turso)** - the in-process SQL database AgentFS builds on -## 📝 License +## License MIT diff --git a/TESTING.md b/TESTING.md deleted file mode 100644 index 60b08804..00000000 --- a/TESTING.md +++ /dev/null @@ -1,710 +0,0 @@ -# Testing AgentFS - -## Phase 8 FUSE concurrency and Git workload gates - -Use the Phase 8 validation scripts when changing FUSE dispatch, kernel cache -policy, OverlayFS/HostFS inode accounting, or AgentFS write batching. These -gates assert the two AgentFS safety principles while measuring the remaining -performance gap against native filesystem operations: - -- the AgentFS database remains portable and inspectable as a single main DB, -- the source/base tree is unchanged after sandboxed writes, -- concurrent Git status/diff/log output matches native output, -- FUSE read dispatch can overlap without the old backend mutex fallback, -- crash/writeback durability tests preserve accepted data or report an - explicitly accepted no-fsync state. - -Recommended fast gate after FUSE/overlay changes: - -```bash -cargo +nightly build --manifest-path cli/Cargo.toml -AGENTFS_FUSE_WORKERS=25% \ - scripts/validation/phase8-validation.py --smoke --timeout 45 -``` - -Focused gates: - -```bash -# Concurrent Git correctness, base immutability, database integrity, portability -AGENTFS_FUSE_WORKERS=25% \ - scripts/validation/phase8-concurrent-git-stress.py \ - --timeout 45 \ - --fixture-files 12 \ - --fixture-dirs 3 \ - --fixture-file-size-bytes 512 \ - --edit-files 2 \ - --append-bytes 32 - -# FUSE read-lane parallelism and global-backend-serialization detection -AGENTFS_FUSE_WORKERS=25% \ - scripts/validation/fuse-serialization-stress.py \ - --timeout 60 \ - --files 8 \ - --file-size-bytes 2048 \ - --threads 4 \ - --iterations 20 \ - --read-bytes 512 - -# Git phase timing breakdown against native -AGENTFS_FUSE_WORKERS=25% \ - scripts/validation/git-workload-benchmark.py \ - --timeout 45 \ - --fixture-files 12 \ - --fixture-dirs 3 \ - --fixture-file-size-bytes 512 \ - --read-files 8 \ - --read-bytes 512 \ - --edit-files 2 \ - --skip-fsck \ - --profile -``` - -Important counters in profile summaries: - -| Counter | Expected meaning | -|---|---| -| `fuse_workers_configured` | Number of configured FUSE workers for the session. | -| `fuse_dispatch_max_concurrent` | Maximum concurrent request callbacks observed. | -| `fuse_read_lane_max_concurrent` | Maximum concurrent read-lane admissions. | -| `fuse_exclusive_fallback_count` | Legacy backend-global mutex fallback count; should be `0` for the direct `Arc` mount path. | -| `fuse_adapter_lock_wait_count` | Legacy backend mutex wait count; should be `0` for the direct mount path. | -| `base_fast_inode_invalidations` | Inode invalidations from FUSE cache/drift handling. | - -For full policy enforcement, run: - -```bash -AGENTFS_FUSE_WORKERS=25% \ - scripts/validation/phase8-validation.py --full --timeout 120 -``` - -The full gate enforces Phase 8 performance thresholds. It is stricter than the -smoke gate and may fail while correctness, portability, and no-real-write gates -pass; use its phase ratios to identify the next optimization target. - -### Validating the default-on kernel cache - -As of Tier One, parallel FUSE dispatch with deferred (off-thread) cache -invalidation is the default, so the kernel cache fast path is engaged -out-of-the-box. Synchronous invalidation (`AGENTFS_FUSE_SYNC_INVAL=1`) is -opt-in because pairing it with parallel workers can deadlock on git -fork/fsync paths (a synchronous notify issued from a request handler blocks -waiting for inline `FUSE_FORGET` traffic that cannot be drained while every -worker lane is busy). To verify the gates pass with **no env vars set** -(the operator's actual experience), run each gate with the AgentFS-prefixed -vars explicitly unset: - -```bash -# Smoke + concurrent-git correctness with default-on cache -env -u AGENTFS_FUSE_WORKERS -u AGENTFS_FUSE_SYNC_INVAL \ - -u AGENTFS_FUSE_WRITEBACK -u AGENTFS_FUSE_KEEPCACHE \ - -u AGENTFS_FUSE_READDIRPLUS -u AGENTFS_FUSE_ENTRY_TTL_MS \ - -u AGENTFS_FUSE_ATTR_TTL_MS -u AGENTFS_FUSE_NEG_TTL_MS \ - scripts/validation/phase8-validation.py --smoke --timeout 60 - -# Robust before/after benchmark wrapper (median + p25/p75 + stdev) -env -u AGENTFS_FUSE_WORKERS -u AGENTFS_FUSE_SYNC_INVAL \ - -u AGENTFS_FUSE_WRITEBACK -u AGENTFS_FUSE_KEEPCACHE \ - -u AGENTFS_FUSE_READDIRPLUS -u AGENTFS_FUSE_ENTRY_TTL_MS \ - -u AGENTFS_FUSE_ATTR_TTL_MS -u AGENTFS_FUSE_NEG_TTL_MS \ - scripts/validation/git-workload-benchmark-multi.py \ - --label default-cache \ - --iterations 5 --warmup 1 \ - --source \ - --read-files 32 --read-bytes 4096 --edit-files 4 --skip-fsck \ - --timeout 600 -``` - -Expected counters in `agentfs_profile_summary` when default-on cache is active: -`fuse_workers_configured > 0`, `fuse_ttl_entry_ms = 1000`, `fuse_ttl_attr_ms = 1000`, -`fuse_writeback_cache_enabled = 1`, `fuse_readdirplus_mode > 0`, and zero -`fuse_exclusive_fallback_count` / `fuse_adapter_lock_wait_count`. Debug builds -also assert in every mutation handler that the kernel cache was invalidated -before the FUSE reply, catching missed invalidations during development. - -## Phase 5.5 read-path benchmark and profiling - -Use `scripts/validation/read-path-benchmark.py` to capture reproducible -native-vs-AgentFS read-path baselines before and after read-path changes. The -script creates a deterministic temporary fixture, runs identical read-only -workloads natively and through `agentfs run`, writes JSON under `/tmp` by -default, and also emits the same JSON to stdout. - -```bash -# Fast smoke with profile summaries/counters -AGENTFS_PROFILE=1 scripts/validation/read-path-benchmark.py \ - --files 8 \ - --dirs 3 \ - --stat-iterations 1 \ - --readdir-iterations 1 \ - --open-iterations 1 \ - --timeout 60 - -# Larger bounded read-path baseline -scripts/validation/read-path-benchmark.py \ - --files 256 \ - --dirs 32 \ - --file-size-bytes 8192 \ - --stat-iterations 8 \ - --readdir-iterations 16 \ - --open-iterations 8 \ - --timeout 180 - -# Only steady warm-session measurement -scripts/validation/read-path-benchmark.py --modes warm --output /tmp/agentfs-read-warm.json -``` - -The benchmark covers: - -- bounded file scan, -- repeated `stat`/`lstat` storm, -- `readdir` storm, -- `readdir_plus` approximation via `os.scandir(...).stat(...)`, -- open/read/close loop, -- cold and warm AgentFS sessions, -- startup/session overhead vs child workload time where measurable. - -Environment: - -| Variable | Description | -|---|---| -| `AGENTFS_BIN` | path/name of the `agentfs` executable | -| `AGENTFS_PROFILE=1` | include parsed `agentfs_profile_summary` lines and counter summaries | -| `READ_PATH_BENCHMARK_MODES` | comma-separated default modes, e.g. `cold,warm` | -| `READ_PATH_BENCHMARK_TIMEOUT` | per-command timeout in seconds | -| `READ_PATH_BENCHMARK_KEEP_TEMP=1` | keep temporary fixture trees and isolated HOME | - -Machine-readable schema (`schema_version: 1`): - -```json -{ - "schema_version": 1, - "benchmark": "phase55-read-path", - "git_commit": "", - "command": { - "argv": ["scripts/validation/read-path-benchmark.py", "..."], - "workload_argv": ["python", "-c", "..."], - "agentfs_prefix": ["/path/to/agentfs", "run", "--session", "", "--no-default-allows", "--"] - }, - "environment": { - "AGENTFS_PROFILE": "1", - "AGENTFS_BIN": "/path/to/agentfs" - }, - "parameters": { - "files": 64, - "dirs": 8, - "file_size_bytes": 4096, - "scan_bytes": 1024, - "stat_iterations": 4, - "readdir_iterations": 8, - "open_iterations": 3, - "open_read_bytes": 512, - "modes": ["cold", "warm"] - }, - "agentfs": { - "bin": "/path/to/agentfs", - "profile_enabled": true, - "profile_summary_count": 4 - }, - "summary": { - "native_seconds": 0.01, - "agentfs_seconds": 0.2, - "ratio": 20.0, - "all_equivalent": true - }, - "modes": [ - { - "mode": "cold", - "session": "read-path-...", - "summary": { - "native_seconds": 0.01, - "agentfs_seconds": 0.2, - "ratio": 20.0 - }, - "steady_state": { - "native_workload_seconds": 0.009, - "agentfs_workload_seconds": 0.15, - "ratio": 16.7 - }, - "equivalence": { - "checked": true, - "equivalent": true, - "native_digest": "...", - "agentfs_digest": "..." - }, - "native": { - "run": {"duration_seconds": 0.01, "returncode": 0}, - "workload": { - "digest": "...", - "phase_seconds": { - "bounded_file_scan": 0.001, - "stat_lstat_storm": 0.001, - "readdir_storm": 0.001, - "readdir_plus_storm": 0.001, - "open_read_close_loop": 0.001 - }, - "counts": {} - }, - "timing": { - "outer_seconds": 0.01, - "workload_seconds": 0.009, - "startup_or_session_overhead_seconds": 0.001 - } - }, - "agentfs": { - "warmup": null, - "run": { - "duration_seconds": 0.2, - "returncode": 0, - "profile_summaries": [] - }, - "workload": {"digest": "...", "phase_seconds": {}, "counts": {}}, - "timing": { - "outer_seconds": 0.2, - "workload_seconds": 0.15, - "startup_or_session_overhead_seconds": 0.05 - }, - "profile_summaries": [], - "profile_counters": { - "summary_count": 2, - "last_by_source": { - "fuse_session": {"fuse_lookup_count": 1}, - "agentfs": {"lookup_count": 1} - }, - "max_counters": { - "lookup_count": 1, - "getattr_count": 1, - "readdir_count": 1, - "readdir_plus_count": 1, - "fuse_callback_count": 1 - } - } - } - } - ], - "temp_dir": "/tmp/agentfs-read-path-benchmark-...", - "kept_temp": false, - "output_path": "/tmp/agentfs-read-path-benchmark-....json" -} -``` - -## Phase 5 profiling and backend-risk helpers - -### Large base-file single-byte edit benchmark - -Use `scripts/validation/large-edit-benchmark.py` to measure the Phase 5 -copy-up risk called out in the north-star spec: a one-byte edit to a large -base-layer file should grow the AgentFS delta database by O(changed chunks), -not O(file size). - -```bash -# Spec-sized run -scripts/validation/large-edit-benchmark.py --file-size-mib 200 --profile - -# Fast smoke -scripts/validation/large-edit-benchmark.py --file-size-mib 1 --timeout 60 - -# Experimental partial-origin smoke -scripts/validation/large-edit-benchmark.py --file-size-mib 1 --partial-origin --timeout 60 -``` - -The helper creates identical native and AgentFS-overlay source trees, warms an -AgentFS session with a metadata-only read pass, performs the same one-byte edit -natively and through `agentfs run`, then emits JSON. The AgentFS DB growth is -measured as the total size of `delta.db` plus any `-wal`/`-shm` files after the -edit minus the same total immediately before the edit. If Python's stdlib -`sqlite3` can open the database, the output also includes `fs_data` row count, -stored chunk bytes, inline inode rows, origin rows, partial-origin rows, -chunk-override rows, and `fs_config`. - -Partial-origin overlay copy-up remains opt-in for Phase 5.5. Use -`--partial-origin` or `AGENTFS_OVERLAY_PARTIAL_ORIGIN=1` to enable it; use -`--no-partial-origin` to force the default whole-file copy-up path even when the -environment variable is set. The JSON output reports the selected mode as -`agentfs.partial_origin_enabled` and echoes the effective env flag under -`agentfs.env_flags.AGENTFS_OVERLAY_PARTIAL_ORIGIN`. - -Machine-readable schema (`schema_version: 1`): - -```json -{ - "schema_version": 1, - "benchmark": "phase5-large-base-single-byte-edit", - "git_commit": "", - "parameters": { - "file_size_bytes": 209715200, - "file_size_mib": 200, - "offset": 104857600, - "edit_width_bytes": 1 - }, - "agentfs": { - "bin": "/path/to/agentfs", - "session": "large-edit-...", - "db_path": "/tmp/.../home/.agentfs/run/.../delta.db", - "profile_enabled": true, - "partial_origin_enabled": false, - "env_flags": {"AGENTFS_OVERLAY_PARTIAL_ORIGIN": null}, - "profile_summary_count": 2 - }, - "database": { - "before_edit": {"total_bytes": 32768, "artifacts": []}, - "after_edit": {"total_bytes": 210000000, "artifacts": []}, - "growth_bytes": 209967232, - "inspect_before": {"inspectable": true}, - "inspect_after": { - "inspectable": true, - "fs_data_rows": 3200, - "fs_data_bytes": 209715200, - "fs_origin_rows": 1, - "fs_partial_origin_rows": 0, - "fs_chunk_override_rows": 0, - "fs_config": {"schema_version": "0.5", "chunk_size": "65536"} - } - }, - "native": {"duration_seconds": 0.1, "run": {}, "result": {}}, - "agentfs_overlay": { - "duration_seconds": 1.2, - "warmup": {}, - "run": {"profile_summaries": []}, - "result": {} - }, - "base_file": { - "original_sha256": "...", - "native_sha256_after": "...", - "agentfs_base_sha256_after": "..." - }, - "correctness": { - "warmup_returncode_zero": true, - "native_returncode_zero": true, - "agentfs_returncode_zero": true, - "outputs_match": true, - "agentfs_base_unchanged": true, - "native_file_changed": true, - "passed": true - } -} -``` - -When `--profile` or `AGENTFS_PROFILE=1` is set, parsed -`agentfs_profile_summary` lines from AgentFS stderr are attached to the -`agentfs_overlay.warmup.profile_summaries` and -`agentfs_overlay.run.profile_summaries` arrays. - -The current recommendation is to keep partial-origin disabled by default. SDK -overlay tests cover remount, main-DB snapshot restore, unlink cleanup, hardlink, -rename/`readdir_plus`, truncate shrink/extend, and drift detection; defaulting -should wait until the same flag is included in supported FUSE/CLI torture and -pjdfstest gates without regressions. - -### Workload baseline profile summaries - -`scripts/validation/workload-baseline.py` also attaches parsed -`agentfs_profile_summary` JSON lines to each AgentFS run as -`iterations[].agentfs.profile_summaries` and reports -`agentfs.profile_summary_count` at the top level. This keeps profiling counters -associated with the native-vs-AgentFS timing and correctness result that -produced them. - -### Backend-risk spike record - -Use `scripts/validation/backend-risk-spike.py` to create a machine-readable -Turso-upgrade/rusqlite-fallback decision record without changing dependencies: - -```bash -scripts/validation/backend-risk-spike.py \ - --candidate-turso-version 0.5.x \ - --output backend-risk.json -``` - -The output records current Cargo dependency state, the candidate Turso version, -the fallback crate under consideration, the minimum storage API surface that a -fallback must cover, validation commands to run in an isolated spike, and -decision fields for measured results. - -#### Phase 5.5 Turso backend spike workflow - -Run dependency-upgrade experiments in an isolated worktree/branch, not the main -worktree: - -```bash -git worktree add ../agentfs-backend-spike -b phase55-backend-spike -cd ../agentfs-backend-spike -``` - -Attempt the candidate Turso upgrade by changing the Rust manifests to the -candidate version/range, then resolve and build with Cargo: - -```bash -cargo check --manifest-path sdk/rust/Cargo.toml -cargo check --manifest-path cli/Cargo.toml -``` - -If the default CLI build is blocked by optional sandbox/nightly-only -dependencies, also run the no-sandbox build to separate backend API breakage -from unrelated optional-feature blockers: - -```bash -cargo check --manifest-path cli/Cargo.toml --no-default-features -``` - -When the candidate builds, run the meaningful gates that are available in the -spike environment: - -```bash -cargo test --manifest-path sdk/rust/Cargo.toml -cargo test --manifest-path cli/Cargo.toml --no-default-features -cli/tests/all.sh -scripts/validation/phase0.sh -scripts/validation/posix/run-pjdfstest.sh \ - --agentfs-bin "$PWD/cli/target/debug/agentfs" \ - --pjdfstest-dir /path/to/pjdfstest \ - --profile phase45-ci -``` - -Record the actual candidate results in JSON. Repeat `--validation-*` options for -each command that was run, and use blockers for exact compiler/API/runtime -failures: - -```bash -scripts/validation/backend-risk-spike.py \ - --candidate-turso-version 0.5.x \ - --resolved-turso-version 0.5.3 \ - --upgrade-built true \ - --validation-result sdk_tests=passed \ - --validation-command 'sdk_tests=cargo test --manifest-path sdk/rust/Cargo.toml' \ - --validation-exit-code sdk_tests=0 \ - --validation-summary 'sdk_tests=130 passed' \ - --validation-result cli_tests=passed \ - --validation-command 'cli_tests=cargo test --manifest-path cli/Cargo.toml --no-default-features' \ - --validation-exit-code cli_tests=0 \ - --validation-summary 'cli_tests=89 passed, 1 ignored' \ - --decision-status upgraded \ - --selected-path turso-upgrade-now \ - --rationale 'Turso 0.5.x built with minimal test expectation updates.' \ - --output /tmp/backend-risk.json -``` - -If the upgrade is blocked, set `--upgrade-built blocked`, add every exact -compiler/API blocker with `--turso-blocker`, and fill the rusqlite fallback -fields: - -```bash -scripts/validation/backend-risk-spike.py \ - --candidate-turso-version 0.5.x \ - --upgrade-built blocked \ - --turso-blocker 'cargo check: exact compiler/API error here' \ - --fallback-trait-practicality 'requires async boundary around open/connect/execute/query/transactions' \ - --fallback-invasiveness 'high: current SDK and CLI directly expose turso Connection, Row, Value, sync Database, and checkpoint/encryption APIs' \ - --fallback-risk-reduction 'useful only if Turso remains blocked after a minimal compatibility patch' \ - --decision-status fallback-required \ - --selected-path rusqlite-fallback-spike \ - --output /tmp/backend-risk.json -``` - -## macOS NFS git validation (#333) - -Use `scripts/validation/macos-nfs-git-validation.sh` on a real macOS host to -validate the NFS CREATE-returned write-handle path used by git loose-object -writes: - -```bash -cd /path/to/agentfs -cargo build --manifest-path cli/Cargo.toml --no-default-features -scripts/validation/macos-nfs-git-validation.sh \ - --agentfs-bin "$PWD/cli/target/debug/agentfs" -``` - -The harness is temp-directory scoped under `/tmp`, initializes a fresh AgentFS -database, mounts it with `agentfs mount --backend nfs`, then runs: - -```bash -git init -git add README.txt -git commit -m "validate macos nfs git loose objects" -git fsck --strict -``` - -It also verifies that the repository produced at least one loose object under -`.git/objects/[0-9a-f][0-9a-f]/`. Expected successful output includes: - -```text -AgentFS binary: /path/to/agentfs -Report directory: /tmp/agentfs-macos-nfs-git-report... -Loose object count: -macOS NFS git validation passed. Logs: /tmp/agentfs-macos-nfs-git-report... -``` - -Unsupported platforms or missing prerequisites exit with `77`; on Linux this is -an expected skip, not a failure. If macOS `mount_nfs` requires privileges in the -local environment, run the same command from a shell where user NFS mounts are -permitted, or inspect the reported `mount.log`. Until this script passes on a -real macOS host, #333 should be treated as code-fixed but platform-validation -pending. - -## Production safety checks - -### Integrity report - -Use `agentfs integrity` to run local SQLite and AgentFS schema checks before -and after risky operations: - -```bash -cargo run --manifest-path cli/Cargo.toml -- integrity .agentfs/my-agent.db --json -``` - -For encrypted databases, pass the same key and cipher used by other CLI -commands: - -```bash -cargo run --manifest-path cli/Cargo.toml -- \ - integrity .agentfs/secure.db --json --key "$AGENTFS_KEY" --cipher aegis256 -``` - -Expected result for a healthy database is JSON with `"ok": true`. A failure -exits nonzero and includes the failed check names, such as -`storage.inline_has_no_chunks` or `namespace.dentry_target_exists`. - -### Verified backup roundtrip - -Use `agentfs backup --verify` to create a portable main-database snapshot. The -command checkpoints/truncates the source WAL, copies the main database file, -reopens the copy, and runs the same integrity checks: - -```bash -cargo run --manifest-path cli/Cargo.toml -- \ - backup .agentfs/my-agent.db /tmp/my-agent-backup.db --verify -``` - -The target file must not already exist. A successful run prints `Checkpoint: -complete`, `Copy: complete`, and `Verification: complete`. - -For encrypted databases, pass `--key` and `--cipher`. Partial-origin overlay -databases are rejected by this portable main-DB backup command because they -depend on an external base tree for non-overridden chunks. - -## pjdfstest - -AgentFS keeps three pjdfstest modes: - -- `phase45-ci`: a conservative, unprivileged supported subset that should pass on the current FUSE implementation. -- `phase5-ci`: the expanded Phase 5 unprivileged supported subset. It includes `phase45-ci` plus additional currently-passing path, FIFO, symlink-loop, sparse large-file, socket-open, and rename/rmdir error-path coverage. -- `full`: the upstream pjdfstest suite, used for exploratory POSIX triage and Phase 5 planning. - -The supported subset intentionally excludes tests that require root-only capabilities (`mknod` for block/char devices, successful `chown`/`lchown`, and alternate uid/gid execution). Those exclusions are tracked in `scripts/validation/posix/pjdfstest/known-gaps.tsv` so Phase 5 can separate unsupported-by-contract gaps from real filesystem bugs. - -### Install pjdfstest locally - -```bash -git clone https://github.com/pjd/pjdfstest.git -cd pjdfstest -autoreconf -ifs -./configure --prefix="$HOME/.local" -make pjdfstest -install -m 0755 pjdfstest "$HOME/.local/bin/pjdfstest" -command -v prove -command -v pjdfstest -``` - -### Run the supported AgentFS gate - -Build the CLI first if needed: - -```bash -cd cli -cargo build -cd .. -``` - -Then run the Phase 4.5 supported profile: - -```bash -scripts/validation/posix/run-pjdfstest.sh \ - --agentfs-bin "$PWD/cli/target/debug/agentfs" \ - --pjdfstest-dir /path/to/pjdfstest \ - --profile phase45-ci -``` - -Expected result: - -```text -All tests successful. -Files=37, Tests=142 -Result: PASS -``` - -The harness writes a report directory containing: - -- `pjdfstest.log` - TAP output from `prove` -- `status.txt` - `prove` exit status -- `selected-profile.txt` - selected profile name -- `selected-manifest.tsv` - selected manifest path and SHA-256 when a manifest-backed profile is used -- `selected-tests.txt` - exact test files run -- `known-unsupported.tsv` - current known POSIX gaps and triage rationale - -Missing prerequisites exit with `77`. A nonzero exit other than `77` means the selected supported profile failed and should be treated as a real regression. - -List supported profiles: - -```bash -scripts/validation/posix/run-pjdfstest.sh --list-profiles -``` - -Run the expanded Phase 5 supported profile: - -```bash -scripts/validation/posix/run-pjdfstest.sh \ - --agentfs-bin "$PWD/cli/target/debug/agentfs" \ - --pjdfstest-dir /path/to/pjdfstest \ - --profile phase5-ci -``` - -Summarize a pjdfstest report and map failures to the known-gap taxonomy: - -```bash -scripts/validation/posix/summarize-pjdfstest-log.py \ - /path/to/pjdfstest.log \ - --known-gaps scripts/validation/posix/pjdfstest/known-gaps.tsv -``` - -### Run the full exploratory suite - -```bash -scripts/validation/posix/run-pjdfstest.sh \ - --agentfs-bin "$PWD/cli/target/debug/agentfs" \ - --pjdfstest-dir /path/to/pjdfstest \ - --profile full -``` - -Full pjdfstest currently exposes known AgentFS POSIX gaps. Use it to expand `known-gaps.tsv` and to choose the next Phase 5 correctness work; do not use `full` as a required CI pass gate until the gaps are resolved or explicitly declared unsupported. - -## xftests - -First, build the `agentfs` executable and install it locally including the `mount.fuse.agentfs` helper: - -```bash -cd cli -cargo build --release -cp target/release/agentfs /usr/local/bin -cp scripts/mount.fuse.agentfs /sbin -``` - -Then, clone the xfstests repo: - -```bash -git clone git://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git -``` - -Configure the filesystem under test: - -```bash -cat local.config -export FSTYP=fuse -export FUSE_SUBTYP=.agentfs -export TEST_DEV= -export TEST_DIR= -``` - -Then, run xfstests: - -```bash -sudo ./check -g quick generic/ -``` diff --git a/cli/Cargo.lock b/cli/Cargo.lock deleted file mode 100644 index e3237407..00000000 --- a/cli/Cargo.lock +++ /dev/null @@ -1,3835 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "cpp_demangle", - "fallible-iterator", - "gimli", - "memmap2", - "object", - "rustc-demangle", - "smallvec", - "typed-arena", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aegis" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78412fa53e6da95324e8902c3641b3ff32ab45258582ea997eb9169c68ffa219" -dependencies = [ - "cc", - "softaes", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "agentfs" -version = "0.6.4" -dependencies = [ - "aegis", - "agentfs-sandbox", - "agentfs-sdk", - "anyhow", - "async-trait", - "base64", - "byteorder", - "chrono", - "clap", - "clap_complete", - "dirs", - "filetime", - "hex", - "libc", - "log", - "memchr", - "nix 0.29.0", - "num-derive", - "num-traits", - "page_size", - "parking_lot", - "reverie", - "reverie-process", - "reverie-ptrace", - "serde", - "serde_json", - "sha1", - "smallvec", - "tempfile", - "tokio", - "tokio-util", - "tracing", - "tracing-subscriber", - "turso", - "uuid", - "zerocopy", -] - -[[package]] -name = "agentfs-sandbox" -version = "0.6.4" -dependencies = [ - "agentfs-sdk", - "anyhow", - "async-trait", - "libc", - "reverie", - "reverie-process", - "reverie-ptrace", - "reverie-util", - "serde", - "tokio", -] - -[[package]] -name = "agentfs-sdk" -version = "0.6.4" -dependencies = [ - "aegis", - "async-trait", - "libc", - "lru", - "parking_lot", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tracing", - "turso", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "antithesis_sdk" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dbd97a5b6c21cc9176891cf715f7f0c273caf3959897f43b9bd1231939e675" -dependencies = [ - "libc", - "libloading", - "linkme", - "once_cell", - "rand 0.8.6", - "rustc_version_runtime", - "serde", - "serde_json", -] - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "arc-swap" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" -dependencies = [ - "rustversion", -] - -[[package]] -name = "assoc" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdc70193dadb9d7287fa4b633f15f90c876915b31f6af17da307fc59c9859a8" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bigdecimal" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" -dependencies = [ - "autocfg", - "libm", - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags 2.11.0", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn", - "which", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" -dependencies = [ - "serde_core", -] - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "branches" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e426eb5cc1900033930ec955317b302e68f19f326cc7bb0c8a86865a826cdf0c" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "built" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" -dependencies = [ - "chrono", -] - -[[package]] -name = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] - -[[package]] -name = "cc" -version = "1.2.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "cfg_block" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18758054972164c3264f7c8386f5fc6da6114cb46b619fd365d4e3b2dc3ae487" - -[[package]] -name = "chrono" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clap" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" -dependencies = [ - "anstream", - "anstyle", - "clap_lex 1.1.0", - "strsim", - "terminal_size", - "unicase", - "unicode-width 0.2.2", -] - -[[package]] -name = "clap_complete" -version = "4.5.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39615915e2ece2550c0149addac32fb5bd312c657f43845bb9088cb9c8a7c992" -dependencies = [ - "clap", - "clap_lex 0.7.7", - "is_executable", - "shlex", -] - -[[package]] -name = "clap_derive" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "close-err" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0dcddd02624b3cf5cafc701a2a69539b8263dd5ed7f5ae6fb7a1ff2f49cbbfe" -dependencies = [ - "kernel32-sys", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "colored" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" -dependencies = [ - "lazy_static", - "windows-sys 0.59.0", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpp_demangle" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32c" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-skiplist" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "typenum", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "env_filter" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" -dependencies = [ - "log", -] - -[[package]] -name = "env_logger" -version = "0.11.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" -dependencies = [ - "env_filter", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fastbloom" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" -dependencies = [ - "getrandom 0.3.4", - "libm", - "rand 0.9.2", - "siphasher", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "filetime" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" -dependencies = [ - "cfg-if", - "libc", - "libredox", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.1", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" - -[[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures 0.1.31", - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "libc", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "genawaiter" -version = "0.99.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86bd0361bcbde39b13475e6e36cb24c329964aa2611be285289d1e4b751c1a0" -dependencies = [ - "genawaiter-macro", -] - -[[package]] -name = "genawaiter-macro" -version = "0.99.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc" - -[[package]] -name = "generator" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows-link", - "windows-result", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "wasip2", - "wasip3", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -dependencies = [ - "fallible-iterator", - "stable_deref_trait", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "goblin" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "983a6aafb3b12d4c41ea78d39e189af4298ce747353945ff5105b54a056e5cd9" -dependencies = [ - "log", - "plain", - "scroll", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "libc", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "iced-x86" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - -[[package]] -name = "intrusive-collections" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" -dependencies = [ - "memoffset", -] - -[[package]] -name = "io-uring" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "libc", -] - -[[package]] -name = "is_executable" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4" -dependencies = [ - "windows-sys 0.60.2", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "js-sys" -version = "0.3.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "libc" -version = "0.2.183" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" - -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libmimalloc-sys" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "libredox" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" -dependencies = [ - "bitflags 2.11.0", - "libc", - "plain", - "redox_syscall 0.7.3", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -dependencies = [ - "serde", -] - -[[package]] -name = "linkme" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83272d46373fb8decca684579ac3e7c8f3d71d4cc3aa693df8759e260ae41cf" -dependencies = [ - "linkme-impl", -] - -[[package]] -name = "linkme-impl" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d59e20403c7d08fe62b4376edfe5c7fb2ef1e6b1465379686d0f21c8df444b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "memmap2" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "miette" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" -dependencies = [ - "cfg-if", - "miette-derive", - "unicode-width 0.1.14", -] - -[[package]] -name = "miette-derive" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "mimalloc" -version = "0.1.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" -dependencies = [ - "libmimalloc-sys", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "never-say-never" -version = "6.6.666" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5a574dadd7941adeaa71823ecba5e28331b8313fb2e1c6a5c7e5981ea53ad6" - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "crc32fast", - "flate2", - "hashbrown 0.15.5", - "indexmap", - "memchr", - "ruzstd", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "openssl" -version = "0.10.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - -[[package]] -name = "pack1" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6e7cd9bd638dc2c831519a0caa1c006cab771a92b1303403a8322773c5b72d6" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "page_size" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" -dependencies = [ - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec", - "windows-link", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "perf-event-open-sys" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fa97b8f69404e792c384914dbe6ffc298586cca534e1c1c410e02f7cd13cfe" -dependencies = [ - "libc", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "procfs" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" -dependencies = [ - "bitflags 2.11.0", - "chrono", - "flate2", - "procfs-core", - "rustix 1.1.4", -] - -[[package]] -name = "procfs-core" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" -dependencies = [ - "bitflags 2.11.0", - "chrono", - "hex", -] - -[[package]] -name = "prost" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" -dependencies = [ - "anyhow", - "itertools 0.14.0", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_pcg" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "rapidhash" -version = "4.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" -dependencies = [ - "rustversion", -] - -[[package]] -name = "raw-cpuid" -version = "10.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "redox_syscall" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" -dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.18", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "reverie" -version = "0.1.0" -source = "git+https://github.com/facebookexperimental/reverie#0cf9bcdf5387280d541f2e35c197916ea7280911" -dependencies = [ - "addr2line", - "anyhow", - "async-trait", - "bitflags 2.11.0", - "byteorder", - "lazy_static", - "libc", - "linked-hash-map", - "memmap2", - "never-say-never", - "nix 0.30.1", - "object", - "procfs", - "raw-cpuid", - "reverie-process", - "reverie-syscalls", - "serde", - "syscalls", - "thiserror 2.0.18", - "typed-arena", -] - -[[package]] -name = "reverie-memory" -version = "0.1.0" -source = "git+https://github.com/facebookexperimental/reverie#0cf9bcdf5387280d541f2e35c197916ea7280911" -dependencies = [ - "syscalls", -] - -[[package]] -name = "reverie-process" -version = "0.1.0" -source = "git+https://github.com/facebookexperimental/reverie#0cf9bcdf5387280d541f2e35c197916ea7280911" -dependencies = [ - "bincode", - "bitflags 2.11.0", - "colored", - "futures 0.3.32", - "libc", - "nix 0.30.1", - "serde", - "syscalls", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "reverie-ptrace" -version = "0.1.0" -source = "git+https://github.com/facebookexperimental/reverie#0cf9bcdf5387280d541f2e35c197916ea7280911" -dependencies = [ - "anyhow", - "async-trait", - "bincode", - "bytes", - "close-err", - "futures 0.3.32", - "goblin", - "iced-x86", - "lazy_static", - "libc", - "nix 0.30.1", - "num-traits", - "paste", - "perf-event-open-sys", - "procfs", - "raw-cpuid", - "reverie", - "safeptrace", - "serde", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", - "tracing-subscriber", - "unwind", -] - -[[package]] -name = "reverie-syscalls" -version = "0.1.0" -source = "git+https://github.com/facebookexperimental/reverie#0cf9bcdf5387280d541f2e35c197916ea7280911" -dependencies = [ - "bitflags 2.11.0", - "derive_more", - "libc", - "nix 0.30.1", - "paste", - "reverie-memory", - "serde", - "syscalls", -] - -[[package]] -name = "reverie-util" -version = "0.1.0" -source = "git+https://github.com/facebookexperimental/reverie#0cf9bcdf5387280d541f2e35c197916ea7280911" -dependencies = [ - "anyhow", - "chrono", - "clap", - "reverie", - "tracing", - "tracing-appender", - "tracing-subscriber", -] - -[[package]] -name = "roaring" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba9ce64a8f45d7fc86358410bb1a82e8c987504c0d4900e9141d69a9f26c885" -dependencies = [ - "bytemuck", - "byteorder", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustc_version_runtime" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dd18cd2bae1820af0b6ad5e54f4a51d0f3fcc53b05f845675074efcc7af071d" -dependencies = [ - "rustc_version", - "semver", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ruzstd" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f" -dependencies = [ - "twox-hash 1.6.3", -] - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "safeptrace" -version = "0.1.0" -source = "git+https://github.com/facebookexperimental/reverie#0cf9bcdf5387280d541f2e35c197916ea7280911" -dependencies = [ - "bitflags 2.11.0", - "futures 0.3.32", - "lazy_static", - "libc", - "nix 0.30.1", - "parking_lot", - "reverie-memory", - "reverie-process", - "syscalls", - "thiserror 2.0.18", -] - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "scroll" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" -dependencies = [ - "scroll_derive", -] - -[[package]] -name = "scroll_derive" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags 2.11.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "shuttle" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab17edba38d63047f46780cf7360acf7467fec2c048928689a5c1dd1c2b4e31" -dependencies = [ - "assoc", - "bitvec", - "cfg-if", - "generator", - "hex", - "owo-colors", - "rand 0.8.6", - "rand_core 0.6.4", - "rand_pcg", - "scoped-tls", - "smallvec", - "tracing", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" - -[[package]] -name = "simsimd" -version = "6.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fb3bc3cdce07a7d7d4caa4c54f8aa967f6be41690482b54b24100a2253fa70" -dependencies = [ - "cc", -] - -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "softaes" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef461faaeb36c340b6c887167a9054a034f6acfc50a014ead26a02b4356b3de" - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syscalls" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d0e35dc7d73976a53c7e6d7d177ef804a0c0ee774ec77bcc520c2216fd7cbe" -dependencies = [ - "serde", - "serde_repr", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.2", - "once_cell", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - -[[package]] -name = "terminal_size" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" -dependencies = [ - "rustix 1.1.4", - "windows-sys 0.61.2", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tokio" -version = "1.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "tracing", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-appender" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" -dependencies = [ - "crossbeam-channel", - "thiserror 2.0.18", - "time", - "tracing-subscriber", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "chrono", - "matchers", - "nu-ansi-term", - "once_cell", - "parking_lot", - "regex-automata", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "time", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "turso" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faba49ac70e21ea35cc963341485f3d17822f2cf433f42152a182117da21d29f" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-tls", - "hyper-util", - "mimalloc", - "thiserror 2.0.18", - "tokio", - "tracing", - "tracing-subscriber", - "turso_sdk_kit", - "turso_sync_sdk_kit", -] - -[[package]] -name = "turso_core" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fac73a12b91b569f4671d63d65912876c11e6312597c996dac40494f9f9b39" -dependencies = [ - "aegis", - "aes", - "aes-gcm", - "antithesis_sdk", - "arc-swap", - "bigdecimal", - "bitflags 2.11.0", - "branches", - "built", - "bumpalo", - "bytemuck", - "cfg_block", - "chrono", - "crc32c", - "crossbeam-skiplist", - "either", - "fallible-iterator", - "fastbloom", - "hex", - "intrusive-collections", - "io-uring", - "libc", - "libloading", - "libm", - "loom", - "miette", - "num-bigint", - "num-traits", - "pack1", - "parking_lot", - "paste", - "polling", - "rand 0.9.2", - "rapidhash", - "regex", - "regex-syntax", - "roaring", - "rustc-hash 2.1.1", - "rustix 1.1.4", - "ryu", - "serde_json", - "shuttle", - "simsimd", - "smallvec", - "strum", - "strum_macros", - "tempfile", - "thiserror 2.0.18", - "tracing", - "tracing-subscriber", - "turso_ext", - "turso_macros", - "turso_parser", - "twox-hash 2.1.2", - "uncased", - "uuid", - "windows-sys 0.61.2", -] - -[[package]] -name = "turso_ext" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd7410a02a3a4cebd48a5bc0db74940d1157dc9c05ad42d48ee5156dd31edd1" -dependencies = [ - "chrono", - "getrandom 0.3.4", - "turso_macros", -] - -[[package]] -name = "turso_macros" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c846c30c3cb085884a8bbaba7760bdcc406ff2176cbde1e51d41b6057171fd4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "turso_parser" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8402ba98c236e3e6d6ed6a43557a9a0b3a682f86a37fcafe02b659b9e6c06b82" -dependencies = [ - "bitflags 2.11.0", - "memchr", - "miette", - "strum", - "strum_macros", - "thiserror 2.0.18", - "turso_macros", -] - -[[package]] -name = "turso_sdk_kit" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b68fee8a6d8515fa6be08ad998d34eba0ac4a8e81dae4b9d0041e21ca01e22" -dependencies = [ - "bindgen", - "env_logger", - "parking_lot", - "tracing", - "tracing-appender", - "tracing-subscriber", - "turso_core", - "turso_sdk_kit_macros", -] - -[[package]] -name = "turso_sdk_kit_macros" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b90fe1bcada9dda8b8e20900f744bdd52f641cccc179f1507e83f8f2ec0b1dc" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "turso_sync_engine" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a94f0d86e6823f63fc52040eb33131ce7fb9cebdb7329a5231443d846e0195a" -dependencies = [ - "base64", - "bytes", - "genawaiter", - "http", - "libc", - "prost", - "roaring", - "serde", - "serde_json", - "thiserror 2.0.18", - "tracing", - "turso_core", - "turso_parser", - "uuid", -] - -[[package]] -name = "turso_sync_sdk_kit" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49fb6c54aaa988f333505a9023fe4985725995b1575eb1557105fa4ac13ea6d" -dependencies = [ - "bindgen", - "env_logger", - "genawaiter", - "parking_lot", - "tracing", - "tracing-appender", - "tracing-subscriber", - "turso_core", - "turso_sdk_kit", - "turso_sdk_kit_macros", - "turso_sync_engine", -] - -[[package]] -name = "twox-hash" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" -dependencies = [ - "cfg-if", - "static_assertions", -] - -[[package]] -name = "twox-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" -dependencies = [ - "rand 0.9.2", -] - -[[package]] -name = "typed-arena" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "uncased" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-segmentation" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da36089a805484bcccfffe0739803392c8298778a2d2f09febf76fac5ad9025b" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - -[[package]] -name = "unwind" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38290439f8459ba56c4bf15fc776463f495fefc4f0112f87a1a075540441b083" -dependencies = [ - "foreign-types 0.5.0", - "libc", - "unwind-sys", -] - -[[package]] -name = "unwind-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a81ba64bc45243d442e9bb2a362f303df152b5078c56ce4a0dc7d813c8df91" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" -dependencies = [ - "getrandom 0.4.2", - "js-sys", - "sha1_smol", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.44", -] - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "zerocopy" -version = "0.8.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/cli/Cargo.toml b/cli/Cargo.toml deleted file mode 100644 index cb20bf37..00000000 --- a/cli/Cargo.toml +++ /dev/null @@ -1,140 +0,0 @@ -[package] -name = "agentfs" -version = "0.6.4" -edition = "2021" -repository = "https://github.com/tursodatabase/agentfs" - -[lib] -name = "agentfs" -path = "src/lib.rs" - -[[bin]] -name = "agentfs" -path = "src/main.rs" - -[features] -default = ["sandbox", "fuse-modern"] -strict = [] -sandbox = [ - "dep:agentfs-sandbox", - "dep:reverie", - "dep:reverie-ptrace", - "dep:reverie-process", -] - -# Vendored fuser is gated per protocol minor version. Enable the full cascade up -# to ABI 7.31 so we can both negotiate the kernel protocol that supports the -# capabilities init() already advertises (FUSE_DO_READDIRPLUS, FUSE_WRITEBACK_CACHE, -# FUSE_PARALLEL_DIROPS, FUSE_CACHE_SYMLINKS, FUSE_NO_OPENDIR_SUPPORT, FUSE_MAX_PAGES, -# FUSE_EXPLICIT_INVAL_DATA, etc.) and actually decode the opcodes the kernel sends -# when those capabilities are negotiated (FUSE_READDIRPLUS=44, FUSE_RENAME2=45, -# FUSE_LSEEK=46, FUSE_COPY_FILE_RANGE=47). Without these, the kernel may issue -# opcode 44 and the dispatcher returns ENOSYS, breaking readdir on the mount. -fuse-modern = [ - "abi-7-19", - "abi-7-20", - "abi-7-21", - "abi-7-22", - "abi-7-23", - "abi-7-24", - "abi-7-25", - "abi-7-26", - "abi-7-27", - "abi-7-28", - "abi-7-29", - "abi-7-30", - "abi-7-31", - "abi-7-36", - "abi-7-41", - "abi-7-42", -] -abi-7-19 = [] -abi-7-20 = [] -abi-7-21 = [] -abi-7-22 = [] -abi-7-23 = [] -abi-7-24 = [] -abi-7-25 = [] -abi-7-26 = [] -abi-7-27 = [] -abi-7-28 = [] -abi-7-29 = [] -abi-7-30 = [] -abi-7-31 = [] -# ABI 7.36-7.42 uplift. The vendored fuser carries a distinct init-struct delta -# only at 7.36 (adds flags2). 7.40 additionally pulls in FUSE_PASSTHROUGH -# scaffolding (FOPEN_PASSTHROUGH / BackingId / open_backing) that is not -# implemented here, so it is intentionally NOT enabled. 7.41/7.42 add no -# init-struct fields — 7.42 only adds the FUSE_OVER_IO_URING capability bit — and -# the 7.36 fuse_init_out is already a kernel-compatible 64 bytes (the trailing -# reserved[7] covers max_stack_depth + request_timeout + unused), so minor 42 is -# advertised on the 7.36 layout, skipping 7.40's passthrough. -abi-7-36 = [] -abi-7-40 = ["abi-7-36"] -abi-7-41 = ["abi-7-36"] -abi-7-42 = ["abi-7-41"] - -[dependencies] -agentfs-sdk = { path = "../sdk/rust" } -tokio = { version = "1", features = ["full"] } -clap = { version = "4", features = ["derive", "env"] } -anyhow = "1.0" -hex = "0.4" -sha1 = "0.10" -turso = { version = "0.5", features = ["sync"] } -serde = { version = "1.0", features = ["derive"] } -parking_lot = "0.12.5" -clap_complete = { version = "=4.5.61", features = ["unstable-dynamic"] } -dirs = "6" -serde_json = "1.0.147" -tracing = "0.1.44" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -chrono = { version = "0.4.42", features = ["serde"] } - -# MCP Server support -base64 = "0.22" - -# NFS server for userspace filesystem (used by `agentfs run` and `agentfs nfs`) -uuid = { version = "1", features = ["v4"] } -tokio-util = "0.7" -async-trait = "0.1" -# Vendored nfsserve dependencies -byteorder = "1.5" -num-derive = "0.4" -num-traits = "0.2" -filetime = "0.2" - -# Unix dependencies -[target.'cfg(unix)'.dependencies] -libc = "0.2" - -# Linux-only dependencies for FUSE and sandbox functionality -[target.'cfg(target_os = "linux")'.dependencies] -# Sandbox dependencies - (requires libunwind-dev on ARM) -agentfs-sandbox = { path = "../sandbox", optional = true } -reverie = { git = "https://github.com/facebookexperimental/reverie", optional = true } -reverie-ptrace = { git = "https://github.com/facebookexperimental/reverie", optional = true } -reverie-process = { git = "https://github.com/facebookexperimental/reverie", optional = true } -# Vendored fuser dependencies (pure-Rust FUSE implementation) -log = "0.4" -memchr = "2.7" -page_size = "0.6" -smallvec = "1.6" -zerocopy = { version = "0.8", features = ["derive"] } -nix = { version = "0.29", features = ["fs", "user"] } - -# macOS dependencies for NFS functionality (no FUSE - uses nfsserve instead) -[target.'cfg(target_os = "macos")'.dependencies] -# Ensure transitive `aegis` (pulled in by `turso`) builds on macOS arm64 by -# forcing the `pure-rust` feature, which skips the C/NEON backend that fails -# with Apple clang (`veor3q_u8` in `aegis128l_neon_sha3.c`). -aegis = { version = "0.9.6", features = ["pure-rust"] } - - -# The profile that 'dist' will build with -[profile.dist] -inherits = "release" -lto = "thin" - -[dev-dependencies] -tempfile = "3.23.0" diff --git a/cli/build.rs b/cli/build.rs deleted file mode 100644 index d782e5cc..00000000 --- a/cli/build.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::process::Command; - -fn main() { - // Sandbox uses libunwind-ptrace which depends on liblzma and gcc_s. - #[cfg(target_os = "linux")] - { - println!("cargo:rustc-link-lib=lzma"); - // libgcc_s provides _Unwind_RaiseException and other exception handling symbols - println!("cargo:rustc-link-lib=dylib=gcc_s"); - } - - // Capture git version from tags for --version flag - // Rerun if git HEAD changes (new commits or tags) - println!("cargo:rerun-if-changed=../.git/HEAD"); - println!("cargo:rerun-if-changed=../.git/refs/tags"); - - let version = Command::new("git") - .args(["describe", "--tags", "--always", "--dirty"]) - .output() - .ok() - .and_then(|output| { - if output.status.success() { - String::from_utf8(output.stdout).ok() - } else { - None - } - }) - .map(|v| v.trim().to_string()) - .unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string()); - - println!("cargo:rustc-env=AGENTFS_VERSION={}", version); -} diff --git a/cli/rust-toolchain.toml b/cli/rust-toolchain.toml deleted file mode 100644 index e14a7cef..00000000 --- a/cli/rust-toolchain.toml +++ /dev/null @@ -1,3 +0,0 @@ -[toolchain] -channel = "nightly" -components = ["rust-analyzer"] diff --git a/cli/src/cmd/mcp_server.rs b/cli/src/cmd/mcp_server.rs deleted file mode 100644 index 006c02e2..00000000 --- a/cli/src/cmd/mcp_server.rs +++ /dev/null @@ -1,956 +0,0 @@ -use agentfs_sdk::{AgentFS, AgentFSOptions, Stats}; - -const S_IFREG: u32 = 0o100000; -use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; -use serde_json::{json, Value as JsonValue}; -use std::collections::HashSet; -use std::io::{self, BufRead, Write}; -use std::path::Path; -use std::sync::Arc; -use tokio::sync::Mutex; - -use crate::cmd::init::open_agentfs; - -/// Main entry point for MCP server command -pub async fn handle_mcp_server_command( - id_or_path: String, - tools_filter: Option>, -) -> Result<()> { - // Resolve and open AgentFS - let options = AgentFSOptions::resolve(&id_or_path).context(format!( - "Failed to resolve agent ID or path: {}", - id_or_path - ))?; - - eprintln!("Using agent: {}", id_or_path); - - let agentfs = open_agentfs(options).await?; - - // Create MCP server with tool filtering - let server = McpServer::new(agentfs, tools_filter); - - // Run server with stdio transport - eprintln!("Starting MCP server on stdio..."); - eprintln!("Protocol: Model Context Protocol (MCP) over JSON-RPC 2.0"); - server.serve().await?; - - Ok(()) -} - -/// MCP Server implementation -struct McpServer { - agentfs: Arc, - enabled_tools: Option>, -} - -impl McpServer { - fn new(agentfs: AgentFS, tools_filter: Option>) -> Self { - let enabled_tools = tools_filter.map(|tools| { - let set: HashSet = tools.into_iter().collect(); - eprintln!("Tool filter enabled. Exposing tools: {:?}", set); - set - }); - - if enabled_tools.is_none() { - eprintln!("No tool filter specified. Exposing all tools."); - } - - Self { - agentfs: Arc::new(agentfs), - enabled_tools, - } - } - - /// Check if a tool is enabled based on filter - fn is_tool_enabled(&self, tool_name: &str) -> bool { - match &self.enabled_tools { - None => true, // No filter = all enabled - Some(set) => set.contains(tool_name), - } - } - - async fn serve(self) -> Result<()> { - let server = Arc::new(Mutex::new(self)); - let stdin = io::stdin(); - let mut stdout = io::stdout(); - - for line in stdin.lock().lines() { - let line = line?; - if line.trim().is_empty() { - continue; - } - - // Parse JSON-RPC request - let request: JsonValue = match serde_json::from_str(&line) { - Ok(req) => req, - Err(e) => { - eprintln!("Failed to parse JSON-RPC request: {}", e); - continue; - } - }; - - // Handle request - let response = server.lock().await.handle_request(request).await; - - // Write response to stdout - if let Some(resp) = response { - let resp_str = serde_json::to_string(&resp)?; - writeln!(stdout, "{}", resp_str)?; - stdout.flush()?; - } - } - - Ok(()) - } - - async fn handle_request(&self, request: JsonValue) -> Option { - // Extract request fields - let method = request.get("method")?.as_str()?; - let id = request.get("id").cloned(); - let params = request.get("params").cloned().unwrap_or(json!({})); - - eprintln!("Received request: method={}", method); - - // Handle method - let result = match method { - "initialize" => self.handle_initialize(params).await, - "initialized" => { - // Notification, no response needed - return None; - } - "tools/list" => self.handle_tools_list().await, - "tools/call" => self.handle_tools_call(params).await, - "resources/list" => self.handle_resources_list().await, - "resources/read" => self.handle_resources_read(params).await, - _ => Err(anyhow::anyhow!("Unknown method: {}", method)), - }; - - // Build JSON-RPC response - let response = match result { - Ok(result) => { - json!({ - "jsonrpc": "2.0", - "id": id, - "result": result - }) - } - Err(e) => { - eprintln!("Error handling {}: {}", method, e); - json!({ - "jsonrpc": "2.0", - "id": id, - "error": { - "code": -32603, - "message": e.to_string() - } - }) - } - }; - - Some(response) - } - - async fn handle_initialize(&self, _params: JsonValue) -> Result { - Ok(json!({ - "protocolVersion": "2024-11-05", - "capabilities": { - "tools": {}, - "resources": {} - }, - "serverInfo": { - "name": "agentfs-mcp-server", - "version": env!("CARGO_PKG_VERSION") - } - })) - } - - async fn handle_tools_list(&self) -> Result { - let tools = self.get_tool_definitions(); - Ok(json!({ "tools": tools })) - } - - async fn handle_tools_call(&self, params: JsonValue) -> Result { - let name = params - .get("name") - .and_then(|v| v.as_str()) - .ok_or_else(|| anyhow::anyhow!("Missing tool name"))?; - - let arguments = params.get("arguments").cloned().unwrap_or(json!({})); - - let result_text = match name { - "read_file" => { - let params: ReadFileParams = serde_json::from_value(arguments)?; - self.handle_read_file(params).await? - } - "write_file" => { - let params: WriteFileParams = serde_json::from_value(arguments)?; - self.handle_write_file(params).await? - } - "readdir" => { - let params: ReaddirParams = serde_json::from_value(arguments)?; - self.handle_readdir(params).await? - } - "mkdir" => { - let params: MkdirParams = serde_json::from_value(arguments)?; - self.handle_mkdir(params).await? - } - "rename" => { - let params: RenameParams = serde_json::from_value(arguments)?; - self.handle_rename(params).await? - } - "remove" => { - let params: RemoveParams = serde_json::from_value(arguments)?; - self.handle_remove(params).await? - } - "stat" => { - let params: StatParams = serde_json::from_value(arguments)?; - self.handle_stat(params).await? - } - "access" => { - let params: AccessParams = serde_json::from_value(arguments)?; - self.handle_access(params).await? - } - "kv_get" => { - let params: KvGetParams = serde_json::from_value(arguments)?; - self.handle_kv_get(params).await? - } - "kv_set" => { - let params: KvSetParams = serde_json::from_value(arguments)?; - self.handle_kv_set(params).await? - } - "kv_delete" => { - let params: KvDeleteParams = serde_json::from_value(arguments)?; - self.handle_kv_delete(params).await? - } - _ => anyhow::bail!("Unknown tool: {}", name), - }; - - Ok(json!({ - "content": [ - { - "type": "text", - "text": result_text - } - ] - })) - } - - async fn handle_resources_list(&self) -> Result { - let resources = self.list_resources().await?; - Ok(json!({ "resources": resources })) - } - - async fn handle_resources_read(&self, params: JsonValue) -> Result { - let uri = params - .get("uri") - .and_then(|v| v.as_str()) - .ok_or_else(|| anyhow::anyhow!("Missing resource uri"))?; - - let contents = self.read_resource(uri).await?; - let mime_type = guess_mime_type(uri); - - // Try to decode as UTF-8, fall back to base64 - let resource_contents = if let Ok(text) = String::from_utf8(contents.clone()) { - json!({ - "uri": uri, - "mimeType": mime_type, - "text": text - }) - } else { - json!({ - "uri": uri, - "mimeType": mime_type, - "blob": base64_encode(&contents) - }) - }; - - Ok(json!({ "contents": [resource_contents] })) - } - - fn get_tool_definitions(&self) -> Vec { - let mut tools = Vec::new(); - - // Filesystem tools - if self.is_tool_enabled("read_file") { - tools.push(json!({ - "name": "read_file", - "description": "Read file contents from the filesystem", - "inputSchema": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the file to read" - }, - "encoding": { - "type": "string", - "enum": ["utf8", "base64"], - "description": "Encoding to use for file contents (default: utf8)" - } - }, - "required": ["path"] - } - })); - } - - if self.is_tool_enabled("write_file") { - tools.push(json!({ - "name": "write_file", - "description": "Write content to a file in the filesystem", - "inputSchema": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the file to write" - }, - "content": { - "type": "string", - "description": "Content to write to the file" - }, - "encoding": { - "type": "string", - "enum": ["utf8", "base64"], - "description": "Encoding of the content (default: utf8)" - }, - "create_dirs": { - "type": "boolean", - "description": "Create parent directories if they don't exist" - } - }, - "required": ["path", "content"] - } - })); - } - - if self.is_tool_enabled("readdir") { - tools.push(json!({ - "name": "readdir", - "description": "List contents of a directory", - "inputSchema": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the directory to list" - } - }, - "required": ["path"] - } - })); - } - - if self.is_tool_enabled("mkdir") { - tools.push(json!({ - "name": "mkdir", - "description": "Create a directory", - "inputSchema": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path of the directory to create" - } - }, - "required": ["path"] - } - })); - } - - if self.is_tool_enabled("remove") { - tools.push(json!({ - "name": "remove", - "description": "Remove a file or empty directory", - "inputSchema": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path of the file or directory to remove" - } - }, - "required": ["path"] - } - })); - } - - if self.is_tool_enabled("rename") { - tools.push(json!({ - "name": "rename", - "description": "Move or rename a file or directory", - "inputSchema": { - "type": "object", - "properties": { - "from": { - "type": "string", - "description": "Source path" - }, - "to": { - "type": "string", - "description": "Destination path" - } - }, - "required": ["from", "to"] - } - })); - } - - if self.is_tool_enabled("stat") { - tools.push(json!({ - "name": "stat", - "description": "Get file or directory metadata", - "inputSchema": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to stat" - } - }, - "required": ["path"] - } - })); - } - - if self.is_tool_enabled("access") { - tools.push(json!({ - "name": "access", - "description": "Test if a path exists", - "inputSchema": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to test" - } - }, - "required": ["path"] - } - })); - } - - // KV store tools - if self.is_tool_enabled("kv_get") { - tools.push(json!({ - "name": "kv_get", - "description": "Get a value from the key-value store", - "inputSchema": { - "type": "object", - "properties": { - "key": { - "type": "string", - "description": "Key to retrieve" - } - }, - "required": ["key"] - } - })); - } - - if self.is_tool_enabled("kv_set") { - tools.push(json!({ - "name": "kv_set", - "description": "Set a value in the key-value store", - "inputSchema": { - "type": "object", - "properties": { - "key": { - "type": "string", - "description": "Key to set" - }, - "value": { - "description": "Value to store (any JSON value)" - } - }, - "required": ["key", "value"] - } - })); - } - - if self.is_tool_enabled("kv_delete") { - tools.push(json!({ - "name": "kv_delete", - "description": "Delete a key from the key-value store", - "inputSchema": { - "type": "object", - "properties": { - "key": { - "type": "string", - "description": "Key to delete" - } - }, - "required": ["key"] - } - })); - } - - if self.is_tool_enabled("kv_list") { - tools.push(json!({ - "name": "kv_list", - "description": "List keys in the key-value store with optional prefix", - "inputSchema": { - "type": "object", - "properties": { - "prefix": { - "type": "string", - "description": "Optional prefix to filter keys" - } - } - } - })); - } - - tools - } -} - -// ============================================================================ -// Helper functions -// ============================================================================ - -/// Normalize a path to ensure it starts with / -fn normalize_path(path: &str) -> Result { - let path = path.trim(); - - // Reject paths with .. for security - if path.contains("..") { - anyhow::bail!("Path traversal not allowed: {}", path); - } - - // Convert relative to absolute - let normalized = if path.starts_with('/') { - path.to_string() - } else if path.is_empty() { - "/".to_string() - } else { - format!("/{}", path) - }; - - Ok(normalized) -} - -/// Guess MIME type based on file extension -fn guess_mime_type(path: &str) -> String { - match Path::new(path).extension().and_then(|s| s.to_str()) { - Some("txt") | Some("md") => "text/plain", - Some("json") => "application/json", - Some("html") | Some("htm") => "text/html", - Some("js") | Some("mjs") => "application/javascript", - Some("ts") => "application/typescript", - Some("css") => "text/css", - Some("xml") => "application/xml", - Some("png") => "image/png", - Some("jpg") | Some("jpeg") => "image/jpeg", - Some("gif") => "image/gif", - Some("svg") => "image/svg+xml", - Some("pdf") => "application/pdf", - Some("zip") => "application/zip", - Some("tar") => "application/x-tar", - Some("gz") => "application/gzip", - _ => "application/octet-stream", - } - .to_string() -} - -/// Base64 encode bytes -fn base64_encode(data: &[u8]) -> String { - use base64::{engine::general_purpose::STANDARD, Engine}; - STANDARD.encode(data) -} - -/// Base64 decode string -fn base64_decode(s: &str) -> Result> { - use base64::{engine::general_purpose::STANDARD, Engine}; - STANDARD - .decode(s) - .map_err(|e| anyhow::anyhow!("Base64 decode error: {}", e)) -} - -// ============================================================================ -// Tool parameter types -// ============================================================================ - -#[derive(Debug, Serialize, Deserialize)] -struct ReadFileParams { - path: String, - #[serde(default)] - encoding: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -struct WriteFileParams { - path: String, - content: String, - #[serde(default)] - encoding: Option, - #[serde(default)] - create_dirs: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -struct ReaddirParams { - path: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct MkdirParams { - path: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct RenameParams { - from: String, - to: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct RemoveParams { - path: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct StatParams { - path: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct AccessParams { - path: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct KvGetParams { - key: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct KvSetParams { - key: String, - value: JsonValue, -} - -#[derive(Debug, Serialize, Deserialize)] -struct KvDeleteParams { - key: String, -} - -// ============================================================================ -// Tool implementations -// ============================================================================ - -impl McpServer { - /// Read file contents - async fn handle_read_file(&self, params: ReadFileParams) -> Result { - let path = normalize_path(¶ms.path)?; - - let data = self - .agentfs - .fs - .read_file(&path) - .await - .context("Failed to read file")? - .ok_or_else(|| anyhow::anyhow!("File not found: {}", path))?; - - let content = match params.encoding.as_deref() { - Some("base64") => base64_encode(&data), - _ => String::from_utf8(data) - .context("File is not valid UTF-8. Use encoding=base64 for binary files.")?, - }; - - Ok(content) - } - - /// Write file contents - async fn handle_write_file(&self, params: WriteFileParams) -> Result { - let path = normalize_path(¶ms.path)?; - - let data = match params.encoding.as_deref() { - Some("base64") => base64_decode(¶ms.content)?, - _ => params.content.into_bytes(), - }; - - // Create parent directories if requested - if params.create_dirs.unwrap_or(false) { - Box::pin(self.ensure_parent_dirs(&path)).await?; - } - - // Remove file if it exists (overwrite behavior) - if self.agentfs.fs.stat(&path).await?.is_some() { - self.agentfs.fs.remove(&path).await?; - } - let (_, file) = self - .agentfs - .fs - .create_file(&path, S_IFREG | 0o644, 0, 0) - .await - .context("Failed to create file")?; - file.pwrite(0, &data) - .await - .context("Failed to write file")?; - - Ok(format!("Wrote {} bytes to {}", data.len(), path)) - } - - /// Helper to create parent directories recursively - fn ensure_parent_dirs<'a>( - &'a self, - path: &'a str, - ) -> std::pin::Pin> + Send + 'a>> { - Box::pin(async move { - let path_obj = Path::new(path); - let parent = match path_obj.parent() { - Some(p) if !p.as_os_str().is_empty() && p != Path::new("/") => p, - _ => return Ok(()), - }; - - let parent_str = parent.to_string_lossy().to_string(); - let parent_path = normalize_path(&parent_str)?; - - // Check if parent exists - if self.agentfs.fs.stat(&parent_path).await?.is_some() { - return Ok(()); - } - - // Recursively ensure grandparent exists - self.ensure_parent_dirs(&parent_path).await?; - - // Create parent - self.agentfs - .fs - .mkdir(&parent_path, 0, 0) - .await - .context(format!("Failed to create directory: {}", parent_path))?; - - Ok(()) - }) - } - - /// List directory contents - async fn handle_readdir(&self, params: ReaddirParams) -> Result { - let path = normalize_path(¶ms.path)?; - - let stats = self - .agentfs - .fs - .stat(&path) - .await - .context("Failed to stat directory")? - .ok_or_else(|| anyhow::anyhow!("Directory not found: {}", path))?; - - let entries = self - .agentfs - .fs - .readdir(stats.ino) - .await - .context("Failed to read directory")? - .ok_or_else(|| anyhow::anyhow!("Directory not found: {}", path))?; - - Ok(serde_json::to_string_pretty(&entries)?) - } - - /// Create directory - async fn handle_mkdir(&self, params: MkdirParams) -> Result { - let path = normalize_path(¶ms.path)?; - - self.agentfs - .fs - .mkdir(&path, 0, 0) - .await - .context("Failed to create directory")?; - - Ok(format!("Created directory: {}", path)) - } - - /// Remove empty directory - async fn handle_remove(&self, params: RemoveParams) -> Result { - let path = normalize_path(¶ms.path)?; - - self.agentfs - .fs - .remove(&path) - .await - .context("Failed to remove directory")?; - - Ok(format!("Removed directory: {}", path)) - } - - /// Rename/move file or directory - async fn handle_rename(&self, params: RenameParams) -> Result { - let from = normalize_path(¶ms.from)?; - let to = normalize_path(¶ms.to)?; - - self.agentfs - .fs - .rename(&from, &to) - .await - .context("Failed to rename")?; - - Ok(format!("Renamed {} to {}", from, to)) - } - - /// Get file metadata - async fn handle_stat(&self, params: StatParams) -> Result { - let path = normalize_path(¶ms.path)?; - - let stats = self - .agentfs - .fs - .stat(&path) - .await - .context("Failed to stat")? - .ok_or_else(|| anyhow::anyhow!("Path not found: {}", path))?; - - Ok(serde_json::to_string_pretty(&StatsResponse::from(stats))?) - } - - /// Test if path exists - async fn handle_access(&self, params: AccessParams) -> Result { - let path = normalize_path(¶ms.path)?; - - let exists = self.agentfs.fs.stat(&path).await?.is_some(); - - Ok(serde_json::to_string(&json!({ "exists": exists }))?) - } - - /// Get KV value - async fn handle_kv_get(&self, params: KvGetParams) -> Result { - let value: Option = self - .agentfs - .kv - .get(¶ms.key) - .await - .context("Failed to get value")?; - - Ok(serde_json::to_string_pretty(&value)?) - } - - /// Set KV value - async fn handle_kv_set(&self, params: KvSetParams) -> Result { - self.agentfs - .kv - .set(¶ms.key, ¶ms.value) - .await - .context("Failed to set value")?; - - Ok(format!("Set key: {}", params.key)) - } - - /// Delete KV value - async fn handle_kv_delete(&self, params: KvDeleteParams) -> Result { - self.agentfs - .kv - .delete(¶ms.key) - .await - .context("Failed to delete key")?; - - Ok(format!("Deleted key: {}", params.key)) - } - - /// List all files as resources - async fn list_resources(&self) -> Result> { - let mut resources = Vec::new(); - Box::pin(self.collect_file_resources("/", &mut resources)).await?; - Ok(resources) - } - - /// Recursively collect file resources - fn collect_file_resources<'a>( - &'a self, - path: &'a str, - resources: &'a mut Vec, - ) -> std::pin::Pin> + Send + 'a>> { - Box::pin(async move { - let dir_stats = match self.agentfs.fs.stat(path).await? { - Some(s) => s, - None => return Ok(()), - }; - - let entries = match self.agentfs.fs.readdir(dir_stats.ino).await? { - Some(entries) => entries, - None => return Ok(()), - }; - - for entry in entries { - let full_path = if path == "/" { - format!("/{}", entry) - } else { - format!("{}/{}", path, entry) - }; - - let stats = match self.agentfs.fs.stat(&full_path).await? { - Some(s) => s, - None => continue, - }; - - if stats.is_file() { - resources.push(json!({ - "uri": full_path, - "name": entry, - "description": format!("File at {}", full_path), - "mimeType": guess_mime_type(&full_path) - })); - } else if stats.is_directory() { - // Recurse into subdirectory - self.collect_file_resources(&full_path, resources).await?; - } - } - - Ok(()) - }) - } - - /// Read a resource by path - async fn read_resource(&self, path: &str) -> Result> { - let normalized = normalize_path(path)?; - - let data = self - .agentfs - .fs - .read_file(&normalized) - .await - .context("Failed to read file")? - .ok_or_else(|| anyhow::anyhow!("File not found: {}", normalized))?; - - Ok(data) - } -} - -// ============================================================================ -// Response types -// ============================================================================ - -#[derive(Debug, Serialize)] -struct StatsResponse { - ino: i64, - mode: u32, - nlink: u32, - uid: u32, - gid: u32, - size: i64, - atime: i64, - mtime: i64, - ctime: i64, - is_file: bool, - is_directory: bool, - is_symlink: bool, -} - -impl From for StatsResponse { - fn from(stats: Stats) -> Self { - Self { - ino: stats.ino, - mode: stats.mode, - nlink: stats.nlink, - uid: stats.uid, - gid: stats.gid, - size: stats.size, - atime: stats.atime, - mtime: stats.mtime, - ctime: stats.ctime, - is_file: stats.is_file(), - is_directory: stats.is_directory(), - is_symlink: stats.is_symlink(), - } - } -} diff --git a/cli/src/cmd/mount_stub.rs b/cli/src/cmd/mount_stub.rs deleted file mode 100644 index 1076f51d..00000000 --- a/cli/src/cmd/mount_stub.rs +++ /dev/null @@ -1,46 +0,0 @@ -use agentfs_sdk::PartialOriginPolicy; -use anyhow::Result; -use std::{io::Write, path::PathBuf}; - -pub use crate::opts::MountBackend; - -/// Arguments for the mount command. -#[derive(Debug, Clone)] -#[allow(dead_code)] -pub struct MountArgs { - /// The agent filesystem ID or path. - pub id_or_path: String, - /// The mountpoint path. - pub mountpoint: PathBuf, - /// Automatically unmount when the process exits. - pub auto_unmount: bool, - /// Allow root to access the mount. - pub allow_root: bool, - /// Allow other system users to access the mount. - pub allow_other: bool, - /// Run in foreground (don't daemonize). - pub foreground: bool, - /// User ID to report for all files (defaults to current user). - pub uid: Option, - /// Group ID to report for all files (defaults to current group). - pub gid: Option, - /// The mount backend to use (fuse or nfs). - pub backend: MountBackend, - /// Partial-origin policy for overlay copy-up. - pub partial_origin_policy: Option, -} - -/// List all currently mounted agentfs filesystems -pub fn list_mounts(out: &mut W) { - let _ = writeln!(out, "Mount listing is only available on Unix."); -} - -/// Mount the agent filesystem. -pub fn mount(_args: MountArgs) -> Result<()> { - anyhow::bail!("Mounting is only available on Unix (Linux or macOS)") -} - -/// Prune unused agentfs mount points. -pub fn prune_mounts(_force: bool) -> Result<()> { - anyhow::bail!("Mount pruning is only available on Unix") -} diff --git a/cli/src/cmd/run.rs b/cli/src/cmd/run.rs deleted file mode 100644 index ba461118..00000000 --- a/cli/src/cmd/run.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Run command - common entry point. -//! -//! Dispatches to platform-specific implementations: -//! - Linux: FUSE + namespace sandbox (or experimental ptrace) -//! - Darwin: NFS + sandbox-exec - -use agentfs_sdk::PartialOriginPolicy; -use anyhow::Result; -use std::path::PathBuf; - -#[cfg_attr(all(target_os = "linux", feature = "sandbox"), path = "run_linux.rs")] -#[cfg_attr(all(target_os = "macos", feature = "sandbox"), path = "run_darwin.rs")] -#[cfg_attr( - all(target_os = "windows", feature = "sandbox"), - path = "run_windows.rs" -)] -#[cfg_attr(not(feature = "sandbox"), path = "run_not_supported.rs")] -mod sys; - -/// Handle the `run` command, dispatching to the platform-specific implementation. -#[allow(clippy::too_many_arguments)] -pub async fn handle_run_command( - allow: Vec, - no_default_allows: bool, - experimental_sandbox: bool, - strace: bool, - session: Option, - system: bool, - encryption: Option<(String, String)>, - partial_origin_policy: Option, - command: PathBuf, - args: Vec, -) -> Result<()> { - sys::run( - allow, - no_default_allows, - experimental_sandbox, - strace, - session, - system, - encryption, - partial_origin_policy, - command, - args, - ) - .await -} diff --git a/cli/src/cmd/run_darwin.rs b/cli/src/cmd/run_darwin.rs deleted file mode 100644 index 6f18f411..00000000 --- a/cli/src/cmd/run_darwin.rs +++ /dev/null @@ -1,477 +0,0 @@ -//! Darwin (macOS) run command implementation. -//! -//! This module provides a sandboxed execution environment using NFS for -//! filesystem mounting. The current working directory becomes a -//! copy-on-write overlay backed by AgentFS, mounted via a localhost NFS server. -//! -//! Sandboxing is enforced using macOS sandbox-exec with dynamically generated -//! profiles that restrict file writes to the NFS mountpoint and allowed paths. - -#![cfg(unix)] - -use agentfs_sdk::{ - AgentFS, AgentFSOptions, EncryptionConfig, FileSystem, HostFS, OverlayFS, PartialOriginPolicy, -}; -use anyhow::{Context, Result}; -use std::path::{Path, PathBuf}; -use std::process::Command; -use std::sync::Arc; - -use crate::nfs::AgentNFS; -use crate::nfsserve::tcp::NFSTcp; - -#[cfg(target_os = "macos")] -use crate::sandbox::darwin::{generate_sandbox_profile, SandboxConfig}; - -/// Default NFS port to try (use a high port to avoid needing root) -const DEFAULT_NFS_PORT: u32 = 11111; - -/// Run the command in a Darwin sandbox. -#[allow(clippy::too_many_arguments)] -pub async fn run( - allow: Vec, - no_default_allows: bool, - _experimental_sandbox: bool, - _strace: bool, - session_id: Option, - _system: bool, - encryption: Option<(String, String)>, - partial_origin_policy: Option, - command: PathBuf, - args: Vec, -) -> Result<()> { - let cwd = std::env::current_dir().context("Failed to get current directory")?; - let home = dirs::home_dir().context("Failed to get home directory")?; - - let session = setup_run_directory(session_id, allow, no_default_allows, &cwd, &home)?; - - // Check if we're joining an existing session - if is_mountpoint(&session.mountpoint) { - if is_mount_healthy(&session.mountpoint) { - eprintln!("Joining existing session: {}", session.session_id); - eprintln!(); - let exit_code = run_command_in_mount(&session, command, args)?; - std::process::exit(exit_code); - } else { - eprintln!("Cleaning up stale NFS mount..."); - if let Err(e) = unmount(&session.mountpoint) { - eprintln!("Warning: Failed to unmount stale mount: {}", e); - } - } - } - - // Initialize the AgentFS database - let db_path_str = session - .db_path - .to_str() - .context("Database path contains non-UTF8 characters")?; - - let encrypted = encryption.is_some(); - let mut options = AgentFSOptions::with_path(db_path_str); - if let Some((key, cipher)) = encryption { - options = options.with_encryption(EncryptionConfig { - hex_key: key, - cipher, - }); - } - let agentfs = AgentFS::open(options) - .await - .context("Failed to create AgentFS")?; - - // Create overlay filesystem with CWD as base - let base_str = cwd.to_string_lossy().to_string(); - let hostfs = HostFS::new(&base_str).context("Failed to create HostFS")?; - let overlay = if let Some(policy) = partial_origin_policy { - OverlayFS::new_with_partial_origin_policy(Arc::new(hostfs), agentfs.fs, policy) - } else { - OverlayFS::new(Arc::new(hostfs), agentfs.fs) - }; - - // Initialize the overlay (copies directory structure) - overlay - .init(&base_str) - .await - .context("Failed to initialize overlay")?; - - let fs: Arc = Arc::new(overlay); - - // Create NFS adapter - let nfs = AgentNFS::new(fs); - - // Find an available port - let port = find_available_port(DEFAULT_NFS_PORT)?; - - // Start NFS server in background - let bind_addr = format!("127.0.0.1:{}", port); - let listener = crate::nfsserve::tcp::NFSTcpListener::bind(&bind_addr, nfs) - .await - .context("Failed to bind NFS server")?; - - // Spawn the NFS server task - let server_handle = tokio::spawn(async move { - if let Err(e) = listener.handle_forever().await { - eprintln!("NFS server error: {}", e); - } - }); - - // Give the server a moment to start - tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - - // Mount the NFS filesystem - mount_nfs(port, &session.mountpoint)?; - - print_welcome_banner(&session, encrypted); - - // Run the command - let exit_code = run_command_in_mount(&session, command, args)?; - - // Unmount - unmount(&session.mountpoint)?; - - // Abort the server task (vendored nfsserve doesn't support graceful shutdown) - server_handle.abort(); - - // Clean up mountpoint directory (but keep the delta database) - if let Err(e) = std::fs::remove_dir(&session.mountpoint) { - eprintln!( - "Warning: Failed to clean up mountpoint {}: {}", - session.mountpoint.display(), - e - ); - } - - // Print session info for the user - eprintln!(); - eprintln!("Session: {}", session.session_id); - eprintln!(); - eprintln!("To resume this session:"); - eprintln!(" agentfs run --session {}", session.session_id); - eprintln!(); - eprintln!("To see what changed:"); - eprintln!(" agentfs diff {}", session.session_id); - - std::process::exit(exit_code); -} - -/// Print the welcome banner showing sandbox configuration (macOS). -#[cfg(target_os = "macos")] -fn print_welcome_banner(session: &RunSession, encrypted: bool) { - use crate::sandbox::group_paths_by_parent; - - eprintln!("Welcome to AgentFS!"); - eprintln!(); - eprintln!("The following directories are writable:"); - eprintln!(); - eprintln!(" - {} (copy-on-write)", session.cwd.display()); - eprintln!(" - /tmp"); - for grouped_path in group_paths_by_parent(&session.allow_paths) { - eprintln!(" - {}", grouped_path); - } - eprintln!(); - eprintln!("🔒 Everything else is read-only."); - if encrypted { - eprintln!("🔐 Delta layer is encrypted."); - } - eprintln!(); - eprintln!("To join this session from another terminal:"); - eprintln!(); - eprintln!(" agentfs run --session {} ", session.session_id); - eprintln!(); -} - -/// Print the welcome banner showing sandbox configuration (Linux). -#[cfg(target_os = "linux")] -fn print_welcome_banner(session: &RunSession, encrypted: bool) { - eprintln!("Welcome to AgentFS!"); - eprintln!(); - eprintln!(" {} (copy-on-write)", session.cwd.display()); - eprintln!(" ⚠️ Everything else is WRITABLE."); - if encrypted { - eprintln!("🔐 Delta layer is encrypted."); - } - eprintln!(); -} - -/// Configuration for a sandbox run session. -struct RunSession { - /// Directory containing session artifacts. - run_dir: PathBuf, - /// Path to the delta database. - db_path: PathBuf, - /// Path where NFS filesystem will be mounted. - mountpoint: PathBuf, - /// Session ID for the sandbox profile. - session_id: String, - /// Additional paths to allow write access. - allow_paths: Vec, - /// Original working directory. - cwd: PathBuf, -} - -/// Default directories in HOME that are allowed to be writable. -/// These are common application config/cache directories that many programs need. -const DEFAULT_ALLOWED_DIRS: &[&str] = &[ - ".amp", // Amp config - ".claude", // Claude Code config - ".claude.json", // Claude Code config file - ".gemini", // Gemini CLI config - ".local", // Local data directory - ".npm", // npm local registry - ".config", // XDG config directory - ".cache", // XDG cache directory - ".bun", // Used by opencode to install packages at runtime -]; - -/// Create a run directory with database and mountpoint paths. -/// -/// If `session_id` is provided, uses that as the run ID (allowing multiple -/// runs to share the same delta layer). Otherwise generates a unique UUID. -fn setup_run_directory( - session_id: Option, - user_allow_paths: Vec, - no_default_allows: bool, - cwd: &Path, - home: &Path, -) -> Result { - let run_id = session_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()); - let run_dir = home.join(".agentfs").join("run").join(&run_id); - std::fs::create_dir_all(&run_dir).context("Failed to create run directory")?; - - let db_path = run_dir.join("delta.db"); - let mountpoint = run_dir.join("mnt"); - std::fs::create_dir_all(&mountpoint).context("Failed to create mountpoint")?; - - // Build allowed paths list - let mut allow_paths = user_allow_paths; - if !no_default_allows { - for dir in DEFAULT_ALLOWED_DIRS { - let path = home.join(dir); - if path.exists() { - allow_paths.push(path); - } - } - } - - // Create zsh config directory with custom prompt - let zsh_dir = run_dir.join("zsh"); - std::fs::create_dir_all(&zsh_dir).context("Failed to create zsh config directory")?; - std::fs::write(zsh_dir.join(".zshrc"), "PROMPT='🤖 %~%# '\n") - .context("Failed to write zsh config")?; - - Ok(RunSession { - run_dir, - db_path, - mountpoint, - session_id: run_id, - allow_paths, - cwd: cwd.to_path_buf(), - }) -} - -/// Check if a path is a mountpoint by comparing device IDs with parent. -fn is_mountpoint(path: &Path) -> bool { - use std::os::unix::fs::MetadataExt; - - let Ok(path_meta) = std::fs::metadata(path) else { - return false; - }; - - let parent = path - .parent() - .filter(|p| !p.as_os_str().is_empty()) - .unwrap_or(Path::new("/")); - - let Ok(parent_meta) = std::fs::metadata(parent) else { - return false; - }; - - path_meta.dev() != parent_meta.dev() -} - -/// Check if a mount is healthy (not stale). -/// -/// Stale NFS mounts will fail when trying to access them. -fn is_mount_healthy(mountpoint: &Path) -> bool { - std::fs::read_dir(mountpoint).is_ok() -} - -/// Find an available TCP port starting from the given port. -fn find_available_port(start_port: u32) -> Result { - for port in start_port..start_port + 100 { - if std::net::TcpListener::bind(format!("127.0.0.1:{}", port)).is_ok() { - return Ok(port); - } - } - anyhow::bail!( - "Could not find an available port in range {}-{}", - start_port, - start_port + 100 - ); -} - -/// Mount the NFS filesystem (macOS version). -#[cfg(target_os = "macos")] -fn mount_nfs(port: u32, mountpoint: &Path) -> Result<()> { - let output = Command::new("/sbin/mount_nfs") - .args([ - "-o", - &format!( - "locallocks,vers=3,tcp,port={},mountport={},wsize=1048576,rsize=1048576,soft,timeo=100,retrans=5", - port, port - ), - "127.0.0.1:/", - mountpoint.to_str().unwrap(), - ]) - .output() - .context("Failed to execute mount_nfs")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("Failed to mount NFS: {}", stderr.trim()); - } - - Ok(()) -} - -/// Mount the NFS filesystem (Linux version). -#[cfg(target_os = "linux")] -fn mount_nfs(port: u32, mountpoint: &Path) -> Result<()> { - let output = Command::new("mount") - .args([ - "-t", - "nfs", - "-o", - &format!( - "vers=3,tcp,port={},mountport={},nolock,soft,timeo=100,retrans=5", - port, port - ), - "127.0.0.1:/", - mountpoint.to_str().unwrap(), - ]) - .output() - .context("Failed to execute mount. Make sure nfs-common is installed.")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!( - "Failed to mount NFS: {}. Make sure nfs-common is installed (apt-get install nfs-common).", - stderr.trim() - ); - } - - Ok(()) -} - -/// Run a command with the working directory set to the mounted filesystem (macOS). -/// -/// On macOS, the command is wrapped with sandbox-exec using a Sandbox profile -/// that restricts file writes to the NFS mountpoint and allowed paths. -/// The mountpoint overlays CWD, and additional paths in HOME are made writable -/// through the allow_paths configuration. -#[cfg(target_os = "macos")] -fn run_command_in_mount(session: &RunSession, command: PathBuf, args: Vec) -> Result { - // Generate the Sandbox profile - let config = SandboxConfig { - mountpoint: session.mountpoint.clone(), - allow_paths: session.allow_paths.clone(), - allow_read_paths: Vec::new(), - allow_network: true, - session_id: session.session_id.clone(), - }; - let profile = generate_sandbox_profile(&config); - - // Wrap the command with sandbox-exec - let mut cmd = Command::new("sandbox-exec"); - cmd.arg("-p") - .arg(&profile) - .arg(&command) - .args(&args) - .current_dir(&session.mountpoint) - .env("AGENTFS", "1") - .env("AGENTFS_SANDBOX", "macos-sandbox") - // Bash prompt - show full path since we're not changing HOME - .env("PS1", "🤖 \\w\\$ ") - // Zsh: use custom ZDOTDIR to override prompt - .env("ZDOTDIR", session.run_dir.join("zsh")); - - let status = cmd - .status() - .with_context(|| format!("Failed to execute command: {}", command.display()))?; - - Ok(status.code().unwrap_or(1)) -} - -/// Run a command with the working directory set to the mounted filesystem (Linux). -/// -/// On Linux, the command runs without additional sandboxing (NFS provides -/// copy-on-write for the working directory). -#[cfg(target_os = "linux")] -fn run_command_in_mount(session: &RunSession, command: PathBuf, args: Vec) -> Result { - let mut cmd = Command::new(&command); - cmd.args(&args) - .current_dir(&session.mountpoint) - .env("AGENTFS", "1") - // Bash prompt - .env("PS1", "🤖 \\u@\\h:\\w\\$ ") - // Zsh: use custom ZDOTDIR to override prompt - .env("ZDOTDIR", session.run_dir.join("zsh")); - - let status = cmd - .status() - .with_context(|| format!("Failed to execute command: {}", command.display()))?; - - Ok(status.code().unwrap_or(1)) -} - -/// Unmount the NFS filesystem (macOS version). -#[cfg(target_os = "macos")] -fn unmount(mountpoint: &Path) -> Result<()> { - let output = Command::new("/sbin/umount") - .arg(mountpoint) - .output() - .context("Failed to execute umount")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - // Try force unmount - let output2 = Command::new("/sbin/umount") - .arg("-f") - .arg(mountpoint) - .output()?; - - if !output2.status.success() { - anyhow::bail!( - "Failed to unmount: {}. You may need to manually unmount with: umount -f {}", - stderr.trim(), - mountpoint.display() - ); - } - } - - Ok(()) -} - -/// Unmount the NFS filesystem (Linux version). -#[cfg(target_os = "linux")] -fn unmount(mountpoint: &Path) -> Result<()> { - let output = Command::new("umount") - .arg(mountpoint) - .output() - .context("Failed to execute umount")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - // Try lazy unmount - let output2 = Command::new("umount").arg("-l").arg(mountpoint).output()?; - - if !output2.status.success() { - anyhow::bail!( - "Failed to unmount: {}. You may need to manually unmount with: umount -l {}", - stderr.trim(), - mountpoint.display() - ); - } - } - - Ok(()) -} diff --git a/cli/src/cmd/run_linux.rs b/cli/src/cmd/run_linux.rs deleted file mode 100644 index fb07cb22..00000000 --- a/cli/src/cmd/run_linux.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Linux run command implementation. -//! -//! Dispatches to either the FUSE+namespace sandbox (default) or the experimental -//! ptrace-based sandbox based on command-line flags. - -use agentfs_sdk::PartialOriginPolicy; -use anyhow::Result; -use std::path::PathBuf; - -/// Run the command in a Linux sandbox. -#[allow(clippy::too_many_arguments)] -pub async fn run( - allow: Vec, - no_default_allows: bool, - experimental_sandbox: bool, - strace: bool, - session: Option, - system: bool, - encryption: Option<(String, String)>, - partial_origin_policy: Option, - command: PathBuf, - args: Vec, -) -> Result<()> { - if experimental_sandbox { - if !allow.is_empty() || no_default_allows { - eprintln!("Warning: --allow and --no-default-allows are not supported with --experimental-sandbox, ignoring"); - } - if session.is_some() { - eprintln!("Warning: --session is not supported with --experimental-sandbox, ignoring"); - } - if encryption.is_some() { - eprintln!("Warning: --key is not supported with --experimental-sandbox, ignoring"); - } - if partial_origin_policy.is_some() { - eprintln!( - "Warning: --partial-origin is not supported with --experimental-sandbox, ignoring" - ); - } - crate::sandbox::linux_ptrace::run_cmd(strace, command, args).await; - } else { - if strace { - eprintln!("Warning: --strace is only supported with --experimental-sandbox, ignoring"); - } - crate::sandbox::linux::run_cmd( - allow, - no_default_allows, - session, - system, - encryption, - partial_origin_policy, - command, - args, - ) - .await?; - } - Ok(()) -} diff --git a/cli/src/cmd/run_not_supported.rs b/cli/src/cmd/run_not_supported.rs deleted file mode 100644 index 3ccbeaea..00000000 --- a/cli/src/cmd/run_not_supported.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Windows run command implementation. -//! -//! The `run` command is not supported on Windows. - -use agentfs_sdk::PartialOriginPolicy; -use anyhow::{bail, Result}; -use std::path::PathBuf; - -/// Run the command in a Windows sandbox. -#[allow(clippy::too_many_arguments)] -pub async fn run( - _allow: Vec, - _no_default_allows: bool, - _experimental_sandbox: bool, - _strace: bool, - _session: Option, - _system: bool, - _encryption: Option<(String, String)>, - _partial_origin_policy: Option, - _command: PathBuf, - _args: Vec, -) -> Result<()> { - bail!("The `run` command require agentfs to be compiled with 'sandbox' feature") -} diff --git a/cli/src/cmd/run_windows.rs b/cli/src/cmd/run_windows.rs deleted file mode 100644 index f9fd35ce..00000000 --- a/cli/src/cmd/run_windows.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Windows run command implementation. -//! -//! The `run` command is not supported on Windows. - -use agentfs_sdk::PartialOriginPolicy; -use anyhow::{bail, Result}; -use std::path::PathBuf; - -/// Run the command in a Windows sandbox. -#[allow(clippy::too_many_arguments)] -pub async fn run( - _allow: Vec, - _no_default_allows: bool, - _experimental_sandbox: bool, - _strace: bool, - _session: Option, - _system: bool, - _encryption: Option<(String, String)>, - _partial_origin_policy: Option, - _command: PathBuf, - _args: Vec, -) -> Result<()> { - bail!("The `run` command is not supported on Windows") -} diff --git a/cli/src/fuser/ll/fuse_abi.rs b/cli/src/fuser/ll/fuse_abi.rs deleted file mode 100644 index a5e38763..00000000 --- a/cli/src/fuser/ll/fuse_abi.rs +++ /dev/null @@ -1,1063 +0,0 @@ -//! FUSE kernel interface. -//! -//! Types and definitions used for communication between the kernel driver and the userspace -//! part of a FUSE filesystem. Since the kernel driver may be installed independently, the ABI -//! interface is versioned and capabilities are exchanged during the initialization (mounting) -//! of a filesystem. -//! -//! OSXFUSE (macOS): -//! - supports ABI 7.8 in OSXFUSE 2.x -//! - supports ABI 7.19 since OSXFUSE 3.0.0 -//! -//! libfuse (Linux/BSD): -//! - supports ABI 7.8 since FUSE 2.6.0 -//! - supports ABI 7.12 since FUSE 2.8.0 -//! - supports ABI 7.18 since FUSE 2.9.0 -//! - supports ABI 7.19 since FUSE 2.9.1 -//! - supports ABI 7.26 since FUSE 3.0.0 -//! -//! Items without a version annotation are valid with ABI 7.8 and later - -#![warn(missing_debug_implementations)] -#![allow(missing_docs)] - -use consts::{FATTR_ATIME_NOW, FATTR_MTIME_NOW}; -use std::convert::TryFrom; -use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; - -pub const FUSE_KERNEL_VERSION: u32 = 7; - -#[cfg(not(feature = "abi-7-19"))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 18; -#[cfg(all(feature = "abi-7-19", not(feature = "abi-7-20")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 19; -#[cfg(all(feature = "abi-7-20", not(feature = "abi-7-21")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 20; -#[cfg(all(feature = "abi-7-21", not(feature = "abi-7-22")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 21; -#[cfg(all(feature = "abi-7-22", not(feature = "abi-7-23")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 22; -#[cfg(all(feature = "abi-7-23", not(feature = "abi-7-24")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 23; -#[cfg(all(feature = "abi-7-24", not(feature = "abi-7-25")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 24; -#[cfg(all(feature = "abi-7-25", not(feature = "abi-7-26")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 25; -#[cfg(all(feature = "abi-7-26", not(feature = "abi-7-27")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 26; -#[cfg(all(feature = "abi-7-27", not(feature = "abi-7-28")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 27; -#[cfg(all(feature = "abi-7-28", not(feature = "abi-7-29")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 28; -#[cfg(all(feature = "abi-7-29", not(feature = "abi-7-30")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 29; -#[cfg(all(feature = "abi-7-30", not(feature = "abi-7-31")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 30; -#[cfg(all(feature = "abi-7-31", not(feature = "abi-7-36")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 31; -#[cfg(all( - feature = "abi-7-36", - not(feature = "abi-7-40"), - not(feature = "abi-7-41") -))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 36; -#[cfg(all(feature = "abi-7-40", not(feature = "abi-7-41")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 40; -#[cfg(all(feature = "abi-7-41", not(feature = "abi-7-42")))] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 41; -#[cfg(feature = "abi-7-42")] -pub const FUSE_KERNEL_MINOR_VERSION: u32 = 42; - -pub const FUSE_ROOT_ID: u64 = 1; - -#[repr(C)] -#[derive(Debug, IntoBytes, Clone, Copy, KnownLayout, Immutable)] -pub struct fuse_attr { - pub ino: u64, - pub size: u64, - pub blocks: u64, - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed - // to match stat.st_atime - pub atime: i64, - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed - // to match stat.st_mtime - pub mtime: i64, - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed - // to match stat.st_ctime - pub ctime: i64, - #[cfg(target_os = "macos")] - pub crtime: u64, - pub atimensec: u32, - pub mtimensec: u32, - pub ctimensec: u32, - #[cfg(target_os = "macos")] - pub crtimensec: u32, - pub mode: u32, - pub nlink: u32, - pub uid: u32, - pub gid: u32, - pub rdev: u32, - #[cfg(target_os = "macos")] - pub flags: u32, // see chflags(2) - pub blksize: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_kstatfs { - pub blocks: u64, // Total blocks (in units of frsize) - pub bfree: u64, // Free blocks - pub bavail: u64, // Free blocks for unprivileged users - pub files: u64, // Total inodes - pub ffree: u64, // Free inodes - pub bsize: u32, // Filesystem block size - pub namelen: u32, // Maximum filename length - pub frsize: u32, // Fundamental file system block size - pub padding: u32, - pub spare: [u32; 6], -} - -#[repr(C)] -#[derive(Debug, IntoBytes, FromBytes, KnownLayout, Immutable)] -pub struct fuse_file_lock { - pub start: u64, - pub end: u64, - // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is treated as signed - pub typ: i32, - pub pid: u32, -} - -pub mod consts { - // Bitmasks for fuse_setattr_in.valid - pub const FATTR_MODE: u32 = 1 << 0; - pub const FATTR_UID: u32 = 1 << 1; - pub const FATTR_GID: u32 = 1 << 2; - pub const FATTR_SIZE: u32 = 1 << 3; - pub const FATTR_ATIME: u32 = 1 << 4; - pub const FATTR_MTIME: u32 = 1 << 5; - pub const FATTR_FH: u32 = 1 << 6; - pub const FATTR_ATIME_NOW: u32 = 1 << 7; - pub const FATTR_MTIME_NOW: u32 = 1 << 8; - pub const FATTR_LOCKOWNER: u32 = 1 << 9; - #[cfg(feature = "abi-7-23")] - pub const FATTR_CTIME: u32 = 1 << 10; - - #[cfg(target_os = "macos")] - pub const FATTR_CRTIME: u32 = 1 << 28; - #[cfg(target_os = "macos")] - pub const FATTR_CHGTIME: u32 = 1 << 29; - #[cfg(target_os = "macos")] - pub const FATTR_BKUPTIME: u32 = 1 << 30; - #[cfg(target_os = "macos")] - pub const FATTR_FLAGS: u32 = 1 << 31; - - // Flags returned by the open request - pub const FOPEN_DIRECT_IO: u32 = 1 << 0; // bypass page cache for this open file - pub const FOPEN_KEEP_CACHE: u32 = 1 << 1; // don't invalidate the data cache on open - pub const FOPEN_NONSEEKABLE: u32 = 1 << 2; // the file is not seekable - pub const FOPEN_CACHE_DIR: u32 = 1 << 3; // allow caching this directory - pub const FOPEN_STREAM: u32 = 1 << 4; // the file is stream-like (no file position at all) - // Init request/reply flags - pub const FUSE_ASYNC_READ: u64 = 1 << 0; // asynchronous read requests - pub const FUSE_POSIX_LOCKS: u64 = 1 << 1; // remote locking for POSIX file locks - pub const FUSE_FILE_OPS: u64 = 1 << 2; // kernel sends file handle for fstat, etc... - pub const FUSE_ATOMIC_O_TRUNC: u64 = 1 << 3; // handles the O_TRUNC open flag in the filesystem - pub const FUSE_EXPORT_SUPPORT: u64 = 1 << 4; // filesystem handles lookups of "." and ".." - pub const FUSE_BIG_WRITES: u64 = 1 << 5; // filesystem can handle write size larger than 4kB - pub const FUSE_DONT_MASK: u64 = 1 << 6; // don't apply umask to file mode on create operations - pub const FUSE_SPLICE_WRITE: u64 = 1 << 7; // kernel supports splice write on the device - pub const FUSE_SPLICE_MOVE: u64 = 1 << 8; // kernel supports splice move on the device - pub const FUSE_SPLICE_READ: u64 = 1 << 9; // kernel supports splice read on the device - pub const FUSE_FLOCK_LOCKS: u64 = 1 << 10; // remote locking for BSD style file locks - pub const FUSE_HAS_IOCTL_DIR: u64 = 1 << 11; // kernel supports ioctl on directories - pub const FUSE_AUTO_INVAL_DATA: u64 = 1 << 12; // automatically invalidate cached pages - pub const FUSE_DO_READDIRPLUS: u64 = 1 << 13; // do READDIRPLUS (READDIR+LOOKUP in one) - pub const FUSE_READDIRPLUS_AUTO: u64 = 1 << 14; // adaptive readdirplus - pub const FUSE_ASYNC_DIO: u64 = 1 << 15; // asynchronous direct I/O submission - pub const FUSE_WRITEBACK_CACHE: u64 = 1 << 16; // use writeback cache for buffered writes - pub const FUSE_NO_OPEN_SUPPORT: u64 = 1 << 17; // kernel supports zero-message opens - pub const FUSE_PARALLEL_DIROPS: u64 = 1 << 18; // allow parallel lookups and readdir - pub const FUSE_HANDLE_KILLPRIV: u64 = 1 << 19; // fs handles killing suid/sgid/cap on write/chown/trunc - pub const FUSE_POSIX_ACL: u64 = 1 << 20; // filesystem supports posix acls - pub const FUSE_ABORT_ERROR: u64 = 1 << 21; // reading the device after abort returns ECONNABORTED - pub const FUSE_MAX_PAGES: u64 = 1 << 22; // init_out.max_pages contains the max number of req pages - pub const FUSE_CACHE_SYMLINKS: u64 = 1 << 23; // cache READLINK responses - pub const FUSE_NO_OPENDIR_SUPPORT: u64 = 1 << 24; // kernel supports zero-message opendir - pub const FUSE_EXPLICIT_INVAL_DATA: u64 = 1 << 25; // only invalidate cached pages on explicit request - pub const FUSE_INIT_EXT: u64 = 1 << 30; // extended fuse_init_in request - pub const FUSE_INIT_RESERVED: u64 = 1 << 31; // reserved, do not use - #[cfg(feature = "abi-7-42")] - pub const FUSE_OVER_IO_URING: u64 = 1 << 41; // client supports fuse-over-io-uring - - // CUSE init request/reply flags - pub const CUSE_UNRESTRICTED_IOCTL: u32 = 1 << 0; // use unrestricted ioctl - - // Release flags - pub const FUSE_RELEASE_FLUSH: u32 = 1 << 0; - pub const FUSE_RELEASE_FLOCK_UNLOCK: u32 = 1 << 1; - - // Getattr flags - pub const FUSE_GETATTR_FH: u32 = 1 << 0; - - // Lock flags - pub const FUSE_LK_FLOCK: u32 = 1 << 0; - - // Write flags - pub const FUSE_WRITE_CACHE: u32 = 1 << 0; // delayed write from page cache, file handle is guessed - pub const FUSE_WRITE_LOCKOWNER: u32 = 1 << 1; // lock_owner field is valid - #[cfg(feature = "abi-7-31")] - pub const FUSE_WRITE_KILL_PRIV: u32 = 1 << 2; // kill suid and sgid bits - - // Read flags - pub const FUSE_READ_LOCKOWNER: u32 = 1 << 1; - - // IOCTL flags - pub const FUSE_IOCTL_COMPAT: u32 = 1 << 0; // 32bit compat ioctl on 64bit machine - pub const FUSE_IOCTL_UNRESTRICTED: u32 = 1 << 1; // not restricted to well-formed ioctls, retry allowed - pub const FUSE_IOCTL_RETRY: u32 = 1 << 2; // retry with new iovecs - pub const FUSE_IOCTL_32BIT: u32 = 1 << 3; // 32bit ioctl - pub const FUSE_IOCTL_DIR: u32 = 1 << 4; // is a directory - #[cfg(feature = "abi-7-30")] - pub const FUSE_IOCTL_COMPAT_X32: u32 = 1 << 5; // x32 compat ioctl on 64bit machine (64bit time_t) - pub const FUSE_IOCTL_MAX_IOV: u32 = 256; // maximum of in_iovecs + out_iovecs - - // Poll flags - pub const FUSE_POLL_SCHEDULE_NOTIFY: u32 = 1 << 0; // request poll notify - - // fsync flags - pub const FUSE_FSYNC_FDATASYNC: u32 = 1 << 0; // Sync data only, not metadata - - // The read buffer is required to be at least 8k, but may be much larger - pub const FUSE_MIN_READ_BUFFER: usize = 8192; -} - -/// Invalid opcode error. -#[derive(Debug)] -pub struct InvalidOpcodeError; - -#[repr(C)] -#[derive(Debug)] -#[allow(non_camel_case_types)] -pub enum fuse_opcode { - FUSE_LOOKUP = 1, - FUSE_FORGET = 2, // no reply - FUSE_GETATTR = 3, - FUSE_SETATTR = 4, - FUSE_READLINK = 5, - FUSE_SYMLINK = 6, - FUSE_MKNOD = 8, - FUSE_MKDIR = 9, - FUSE_UNLINK = 10, - FUSE_RMDIR = 11, - FUSE_RENAME = 12, - FUSE_LINK = 13, - FUSE_OPEN = 14, - FUSE_READ = 15, - FUSE_WRITE = 16, - FUSE_STATFS = 17, - FUSE_RELEASE = 18, - FUSE_FSYNC = 20, - FUSE_SETXATTR = 21, - FUSE_GETXATTR = 22, - FUSE_LISTXATTR = 23, - FUSE_REMOVEXATTR = 24, - FUSE_FLUSH = 25, - FUSE_INIT = 26, - FUSE_OPENDIR = 27, - FUSE_READDIR = 28, - FUSE_RELEASEDIR = 29, - FUSE_FSYNCDIR = 30, - FUSE_GETLK = 31, - FUSE_SETLK = 32, - FUSE_SETLKW = 33, - FUSE_ACCESS = 34, - FUSE_CREATE = 35, - FUSE_INTERRUPT = 36, - FUSE_BMAP = 37, - FUSE_DESTROY = 38, - FUSE_IOCTL = 39, - FUSE_POLL = 40, - FUSE_NOTIFY_REPLY = 41, - FUSE_BATCH_FORGET = 42, - #[cfg(feature = "abi-7-19")] - FUSE_FALLOCATE = 43, - #[cfg(feature = "abi-7-21")] - FUSE_READDIRPLUS = 44, - #[cfg(feature = "abi-7-23")] - FUSE_RENAME2 = 45, - #[cfg(feature = "abi-7-24")] - FUSE_LSEEK = 46, - #[cfg(feature = "abi-7-28")] - FUSE_COPY_FILE_RANGE = 47, - - #[cfg(target_os = "macos")] - FUSE_SETVOLNAME = 61, - #[cfg(target_os = "macos")] - FUSE_GETXTIMES = 62, - #[cfg(target_os = "macos")] - FUSE_EXCHANGE = 63, - - CUSE_INIT = 4096, -} - -impl TryFrom for fuse_opcode { - type Error = InvalidOpcodeError; - - fn try_from(n: u32) -> Result { - match n { - 1 => Ok(fuse_opcode::FUSE_LOOKUP), - 2 => Ok(fuse_opcode::FUSE_FORGET), - 3 => Ok(fuse_opcode::FUSE_GETATTR), - 4 => Ok(fuse_opcode::FUSE_SETATTR), - 5 => Ok(fuse_opcode::FUSE_READLINK), - 6 => Ok(fuse_opcode::FUSE_SYMLINK), - 8 => Ok(fuse_opcode::FUSE_MKNOD), - 9 => Ok(fuse_opcode::FUSE_MKDIR), - 10 => Ok(fuse_opcode::FUSE_UNLINK), - 11 => Ok(fuse_opcode::FUSE_RMDIR), - 12 => Ok(fuse_opcode::FUSE_RENAME), - 13 => Ok(fuse_opcode::FUSE_LINK), - 14 => Ok(fuse_opcode::FUSE_OPEN), - 15 => Ok(fuse_opcode::FUSE_READ), - 16 => Ok(fuse_opcode::FUSE_WRITE), - 17 => Ok(fuse_opcode::FUSE_STATFS), - 18 => Ok(fuse_opcode::FUSE_RELEASE), - 20 => Ok(fuse_opcode::FUSE_FSYNC), - 21 => Ok(fuse_opcode::FUSE_SETXATTR), - 22 => Ok(fuse_opcode::FUSE_GETXATTR), - 23 => Ok(fuse_opcode::FUSE_LISTXATTR), - 24 => Ok(fuse_opcode::FUSE_REMOVEXATTR), - 25 => Ok(fuse_opcode::FUSE_FLUSH), - 26 => Ok(fuse_opcode::FUSE_INIT), - 27 => Ok(fuse_opcode::FUSE_OPENDIR), - 28 => Ok(fuse_opcode::FUSE_READDIR), - 29 => Ok(fuse_opcode::FUSE_RELEASEDIR), - 30 => Ok(fuse_opcode::FUSE_FSYNCDIR), - 31 => Ok(fuse_opcode::FUSE_GETLK), - 32 => Ok(fuse_opcode::FUSE_SETLK), - 33 => Ok(fuse_opcode::FUSE_SETLKW), - 34 => Ok(fuse_opcode::FUSE_ACCESS), - 35 => Ok(fuse_opcode::FUSE_CREATE), - 36 => Ok(fuse_opcode::FUSE_INTERRUPT), - 37 => Ok(fuse_opcode::FUSE_BMAP), - 38 => Ok(fuse_opcode::FUSE_DESTROY), - 39 => Ok(fuse_opcode::FUSE_IOCTL), - 40 => Ok(fuse_opcode::FUSE_POLL), - 41 => Ok(fuse_opcode::FUSE_NOTIFY_REPLY), - 42 => Ok(fuse_opcode::FUSE_BATCH_FORGET), - #[cfg(feature = "abi-7-19")] - 43 => Ok(fuse_opcode::FUSE_FALLOCATE), - #[cfg(feature = "abi-7-21")] - 44 => Ok(fuse_opcode::FUSE_READDIRPLUS), - #[cfg(feature = "abi-7-23")] - 45 => Ok(fuse_opcode::FUSE_RENAME2), - #[cfg(feature = "abi-7-24")] - 46 => Ok(fuse_opcode::FUSE_LSEEK), - #[cfg(feature = "abi-7-28")] - 47 => Ok(fuse_opcode::FUSE_COPY_FILE_RANGE), - - #[cfg(target_os = "macos")] - 61 => Ok(fuse_opcode::FUSE_SETVOLNAME), - #[cfg(target_os = "macos")] - 62 => Ok(fuse_opcode::FUSE_GETXTIMES), - #[cfg(target_os = "macos")] - 63 => Ok(fuse_opcode::FUSE_EXCHANGE), - - 4096 => Ok(fuse_opcode::CUSE_INIT), - - _ => Err(InvalidOpcodeError), - } - } -} - -/// Invalid notify code error. -#[derive(Debug)] -pub struct InvalidNotifyCodeError; - -#[repr(C)] -#[derive(Debug)] -#[allow(non_camel_case_types)] -pub enum fuse_notify_code { - FUSE_POLL = 1, - FUSE_NOTIFY_INVAL_INODE = 2, - FUSE_NOTIFY_INVAL_ENTRY = 3, - FUSE_NOTIFY_STORE = 4, - FUSE_NOTIFY_RETRIEVE = 5, - FUSE_NOTIFY_DELETE = 6, -} - -impl TryFrom for fuse_notify_code { - type Error = InvalidNotifyCodeError; - - fn try_from(n: u32) -> Result { - match n { - 1 => Ok(fuse_notify_code::FUSE_POLL), - 2 => Ok(fuse_notify_code::FUSE_NOTIFY_INVAL_INODE), - 3 => Ok(fuse_notify_code::FUSE_NOTIFY_INVAL_ENTRY), - 4 => Ok(fuse_notify_code::FUSE_NOTIFY_STORE), - 5 => Ok(fuse_notify_code::FUSE_NOTIFY_RETRIEVE), - 6 => Ok(fuse_notify_code::FUSE_NOTIFY_DELETE), - - _ => Err(InvalidNotifyCodeError), - } - } -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_entry_out { - pub nodeid: u64, - pub generation: u64, - pub entry_valid: u64, - pub attr_valid: u64, - pub entry_valid_nsec: u32, - pub attr_valid_nsec: u32, - pub attr: fuse_attr, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_forget_in { - pub nlookup: u64, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_forget_one { - pub nodeid: u64, - pub nlookup: u64, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_batch_forget_in { - pub count: u32, - pub dummy: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_getattr_in { - pub getattr_flags: u32, - pub dummy: u32, - pub fh: u64, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_attr_out { - pub attr_valid: u64, - pub attr_valid_nsec: u32, - pub dummy: u32, - pub attr: fuse_attr, -} - -#[cfg(target_os = "macos")] -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_getxtimes_out { - pub bkuptime: u64, - pub crtime: u64, - pub bkuptimensec: u32, - pub crtimensec: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_mknod_in { - pub mode: u32, - pub rdev: u32, - pub umask: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_mkdir_in { - pub mode: u32, - pub umask: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_rename_in { - pub newdir: u64, - #[cfg(feature = "macfuse-4-compat")] - pub flags: u32, - #[cfg(feature = "macfuse-4-compat")] - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_rename2_in { - pub newdir: u64, - pub flags: u32, - pub padding: u32, -} - -#[cfg(target_os = "macos")] -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_exchange_in { - pub olddir: u64, - pub newdir: u64, - pub options: u64, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_link_in { - pub oldnodeid: u64, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_setattr_in { - pub valid: u32, - pub padding: u32, - pub fh: u64, - pub size: u64, - pub lock_owner: u64, - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed - // to match stat.st_atime - pub atime: i64, - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed - // to match stat.st_mtime - pub mtime: i64, - #[cfg(not(feature = "abi-7-23"))] - pub unused2: u64, - #[cfg(feature = "abi-7-23")] - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed - // to match stat.st_ctime - pub ctime: i64, - pub atimensec: u32, - pub mtimensec: u32, - #[cfg(not(feature = "abi-7-23"))] - pub unused3: u32, - #[cfg(feature = "abi-7-23")] - pub ctimensec: u32, - pub mode: u32, - pub unused4: u32, - pub uid: u32, - pub gid: u32, - pub unused5: u32, - #[cfg(target_os = "macos")] - pub bkuptime: u64, - #[cfg(target_os = "macos")] - pub chgtime: u64, - #[cfg(target_os = "macos")] - pub crtime: u64, - #[cfg(target_os = "macos")] - pub bkuptimensec: u32, - #[cfg(target_os = "macos")] - pub chgtimensec: u32, - #[cfg(target_os = "macos")] - pub crtimensec: u32, - #[cfg(target_os = "macos")] - pub flags: u32, // see chflags(2) -} - -impl fuse_setattr_in { - pub fn atime_now(&self) -> bool { - self.valid & FATTR_ATIME_NOW != 0 - } - - pub fn mtime_now(&self) -> bool { - self.valid & FATTR_MTIME_NOW != 0 - } -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_open_in { - // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast - // to an i32 when invoking the filesystem's open method and this matches the open() syscall - pub flags: i32, - pub unused: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_create_in { - // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast - // to an i32 when invoking the filesystem's create method and this matches the open() syscall - pub flags: i32, - pub mode: u32, - pub umask: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_create_out(pub fuse_entry_out, pub fuse_open_out); - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_open_out { - pub fh: u64, - pub open_flags: u32, - #[cfg(not(feature = "abi-7-40"))] - pub padding: u32, - #[cfg(feature = "abi-7-40")] - pub backing_id: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_release_in { - pub fh: u64, - // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast - // to an i32 when invoking the filesystem's read method - pub flags: i32, - pub release_flags: u32, - pub lock_owner: u64, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_flush_in { - pub fh: u64, - pub unused: u32, - pub padding: u32, - pub lock_owner: u64, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_read_in { - pub fh: u64, - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is then cast - // to an i64 when invoking the filesystem's read method - pub offset: i64, - pub size: u32, - pub read_flags: u32, - pub lock_owner: u64, - // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast - // to an i32 when invoking the filesystem's read method - pub flags: i32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_write_in { - pub fh: u64, - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is then cast - // to an i64 when invoking the filesystem's write method - pub offset: i64, - pub size: u32, - pub write_flags: u32, - pub lock_owner: u64, - // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast - // to an i32 when invoking the filesystem's read method - pub flags: i32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_write_out { - pub size: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_statfs_out { - pub st: fuse_kstatfs, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_fsync_in { - pub fh: u64, - pub fsync_flags: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_setxattr_in { - pub size: u32, - // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast - // to an i32 when invoking the filesystem's setxattr method - pub flags: i32, - #[cfg(target_os = "macos")] - pub position: u32, - #[cfg(target_os = "macos")] - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_getxattr_in { - pub size: u32, - pub padding: u32, - #[cfg(target_os = "macos")] - pub position: u32, - #[cfg(target_os = "macos")] - pub padding2: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_getxattr_out { - pub size: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_lk_in { - pub fh: u64, - pub owner: u64, - pub lk: fuse_file_lock, - pub lk_flags: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_lk_out { - pub lk: fuse_file_lock, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_access_in { - // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast - // to an i32 when invoking the filesystem's access method - pub mask: i32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_init_in { - pub major: u32, - pub minor: u32, - pub max_readahead: u32, - pub flags: u32, - #[cfg(feature = "abi-7-36")] - pub flags2: u32, - #[cfg(feature = "abi-7-36")] - pub unused: [u32; 11], -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_init_out { - pub major: u32, - pub minor: u32, - pub max_readahead: u32, - pub flags: u32, - pub max_background: u16, - pub congestion_threshold: u16, - pub max_write: u32, - #[cfg(feature = "abi-7-23")] - pub time_gran: u32, - #[cfg(all(feature = "abi-7-23", not(feature = "abi-7-28")))] - pub reserved: [u32; 9], - #[cfg(feature = "abi-7-28")] - pub max_pages: u16, - #[cfg(feature = "abi-7-28")] - pub unused2: u16, - #[cfg(all(feature = "abi-7-28", not(feature = "abi-7-36")))] - pub reserved: [u32; 8], - #[cfg(feature = "abi-7-36")] - pub flags2: u32, - #[cfg(all(feature = "abi-7-36", not(feature = "abi-7-40")))] - pub reserved: [u32; 7], - #[cfg(feature = "abi-7-40")] - pub max_stack_depth: u32, - #[cfg(feature = "abi-7-40")] - pub reserved: [u32; 6], -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct cuse_init_in { - pub major: u32, - pub minor: u32, - pub unused: u32, - pub flags: u32, -} - -#[repr(C)] -#[derive(Debug, KnownLayout, Immutable)] -pub struct cuse_init_out { - pub major: u32, - pub minor: u32, - pub unused: u32, - pub flags: u32, - pub max_read: u32, - pub max_write: u32, - pub dev_major: u32, // chardev major - pub dev_minor: u32, // chardev minor - pub spare: [u32; 10], -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_interrupt_in { - pub unique: u64, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_bmap_in { - pub block: u64, - pub blocksize: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_bmap_out { - pub block: u64, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_ioctl_in { - pub fh: u64, - pub flags: u32, - pub cmd: u32, - pub arg: u64, // TODO: this is currently unused, but is defined as a void* in libfuse - pub in_size: u32, - pub out_size: u32, -} - -#[repr(C)] -#[derive(Debug, KnownLayout, Immutable)] -pub struct fuse_ioctl_iovec { - pub base: u64, - pub len: u64, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_ioctl_out { - pub result: i32, - pub flags: u32, - pub in_iovs: u32, - pub out_iovs: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_poll_in { - pub fh: u64, - pub kh: u64, - pub flags: u32, - #[cfg(not(feature = "abi-7-21"))] - pub padding: u32, - #[cfg(feature = "abi-7-21")] - pub events: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_poll_out { - pub revents: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_notify_poll_wakeup_out { - pub kh: u64, -} - -#[cfg(feature = "abi-7-19")] -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_fallocate_in { - pub fh: u64, - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed - pub offset: i64, - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed - pub length: i64, - // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is treated as signed - pub mode: i32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_in_header { - pub len: u32, - pub opcode: u32, - pub unique: u64, - pub nodeid: u64, - pub uid: u32, - pub gid: u32, - pub pid: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_out_header { - pub len: u32, - pub error: i32, - pub unique: u64, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_dirent { - pub ino: u64, - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed - pub off: i64, - pub namelen: u32, - pub typ: u32, - // followed by name of namelen bytes -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_direntplus { - pub entry_out: fuse_entry_out, - pub dirent: fuse_dirent, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_notify_inval_inode_out { - pub ino: u64, - pub off: i64, - pub len: i64, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_notify_inval_entry_out { - pub parent: u64, - pub namelen: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_notify_delete_out { - pub parent: u64, - pub child: u64, - pub namelen: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_notify_store_out { - pub nodeid: u64, - pub offset: u64, - pub size: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, KnownLayout, Immutable)] -pub struct fuse_notify_retrieve_out { - pub notify_unique: u64, - pub nodeid: u64, - pub offset: u64, - pub size: u32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_notify_retrieve_in { - // matches the size of fuse_write_in - pub dummy1: u64, - pub offset: u64, - pub size: u32, - pub dummy2: u32, - pub dummy3: u64, - pub dummy4: u64, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_lseek_in { - pub fh: u64, - pub offset: i64, - // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is treated as signed - pub whence: i32, - pub padding: u32, -} - -#[repr(C)] -#[derive(Debug, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_lseek_out { - pub offset: i64, -} - -#[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] -pub struct fuse_copy_file_range_in { - pub fh_in: u64, - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed - pub off_in: i64, - pub nodeid_out: u64, - pub fh_out: u64, - // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed - pub off_out: i64, - pub len: u64, - pub flags: u64, -} - -// FUSE-over-io-uring (ABI 7.42). Mirrors the uapi in . The header -// (in_out + op_in + ring_ent_in_out) lives in a page-aligned per-entry buffer -// the kernel reads requests into / we write replies into; the variable payload -// lives in a separate per-entry buffer. See cli/src/fuser/uring.rs. -#[cfg(feature = "abi-7-42")] -pub const FUSE_URING_IN_OUT_HEADER_SZ: usize = 128; -#[cfg(feature = "abi-7-42")] -pub const FUSE_URING_OP_IN_OUT_SZ: usize = 128; - -// Subcommands carried in the SQE cmd_op field. -#[cfg(feature = "abi-7-42")] -pub const FUSE_IO_URING_CMD_REGISTER: u32 = 1; -#[cfg(feature = "abi-7-42")] -pub const FUSE_IO_URING_CMD_COMMIT_AND_FETCH: u32 = 2; - -#[cfg(feature = "abi-7-42")] -#[repr(C)] -#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_uring_ent_in_out { - pub flags: u64, - /// commit ID to be used in a reply to a ring request - pub commit_id: u64, - /// size of user payload buffer - pub payload_sz: u32, - pub padding: u32, - pub reserved: u64, -} - -#[cfg(feature = "abi-7-42")] -#[repr(C)] -#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_uring_req_header { - /// struct fuse_in_header / struct fuse_out_header - pub in_out: [u8; FUSE_URING_IN_OUT_HEADER_SZ], - /// per-opcode header - pub op_in: [u8; FUSE_URING_OP_IN_OUT_SZ], - pub ring_ent_in_out: fuse_uring_ent_in_out, -} - -/// In the 80B command area of the SQE. -#[cfg(feature = "abi-7-42")] -#[repr(C)] -#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] -pub struct fuse_uring_cmd_req { - pub flags: u64, - /// entry identifier for commits - pub commit_id: u64, - /// queue the command is for (queue index) - pub qid: u16, - pub padding: [u8; 6], -} diff --git a/cli/src/fuser/ll/mod.rs b/cli/src/fuser/ll/mod.rs deleted file mode 100644 index 624b01c2..00000000 --- a/cli/src/fuser/ll/mod.rs +++ /dev/null @@ -1,306 +0,0 @@ -//! Low-level kernel communication. - -mod argument; -pub mod fuse_abi; -pub(crate) mod notify; -pub(crate) mod reply; -pub(crate) mod request; - -use std::{convert::TryInto, num::NonZeroI32, time::SystemTime}; - -pub use reply::Response; -pub use request::{AnyRequest, FileHandle, INodeNo, Lock, Operation, Request, RequestId, Version}; - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -/// Possible input arguments for atime & mtime, which can either be set to a specified time, -/// or to the current time -pub enum TimeOrNow { - /// Specific time provided - SpecificTime(SystemTime), - /// Current time - Now, -} - -macro_rules! errno { - ($x: expr_2021) => { - Errno(unsafe { - // This is a static assertion that the constant $x is > 0 - const _X: [(); 0 - !{ - const ASSERT: bool = ($x > 0); - ASSERT - } as usize] = []; - // Which makes this safe - NonZeroI32::new_unchecked($x) - }) - }; -} - -macro_rules! no_xattr_doc { - () => {"Use this as an error return from getxattr/removexattr to indicate that the xattr doesn't exist. This resolves to the appropriate platform-specific error code."} -} - -/// Represents an error code to be returned to the caller -#[derive(Debug)] -pub struct Errno(pub NonZeroI32); -impl Errno { - /// Operation not permitted - pub const EPERM: Errno = errno!(libc::EPERM); - /// No such file or directory - pub const ENOENT: Errno = errno!(libc::ENOENT); - /// No such process - pub const ESRCH: Errno = errno!(libc::ESRCH); - /// Interrupted system call - pub const EINTR: Errno = errno!(libc::EINTR); - /// Input/output error - pub const EIO: Errno = errno!(libc::EIO); - /// No such device or address - pub const ENXIO: Errno = errno!(libc::ENXIO); - /// Argument list too long - pub const E2BIG: Errno = errno!(libc::E2BIG); - /// Exec format error - pub const ENOEXEC: Errno = errno!(libc::ENOEXEC); - /// Bad file descriptor - pub const EBADF: Errno = errno!(libc::EBADF); - /// No child processes - pub const ECHILD: Errno = errno!(libc::ECHILD); - /// Resource temporarily unavailable - pub const EAGAIN: Errno = errno!(libc::EAGAIN); - /// Cannot allocate memory - pub const ENOMEM: Errno = errno!(libc::ENOMEM); - /// Permission denied - pub const EACCES: Errno = errno!(libc::EACCES); - /// Bad address - pub const EFAULT: Errno = errno!(libc::EFAULT); - /// Block device required - pub const ENOTBLK: Errno = errno!(libc::ENOTBLK); - /// Device or resource busy - pub const EBUSY: Errno = errno!(libc::EBUSY); - /// File exists - pub const EEXIST: Errno = errno!(libc::EEXIST); - /// Invalid cross-device link - pub const EXDEV: Errno = errno!(libc::EXDEV); - /// No such device - pub const ENODEV: Errno = errno!(libc::ENODEV); - /// Not a directory - pub const ENOTDIR: Errno = errno!(libc::ENOTDIR); - /// Is a directory - pub const EISDIR: Errno = errno!(libc::EISDIR); - /// Invalid argument - pub const EINVAL: Errno = errno!(libc::EINVAL); - /// Too many open files in system - pub const ENFILE: Errno = errno!(libc::ENFILE); - /// Too many open files - pub const EMFILE: Errno = errno!(libc::EMFILE); - /// Inappropriate ioctl for device - pub const ENOTTY: Errno = errno!(libc::ENOTTY); - /// Text file busy - pub const ETXTBSY: Errno = errno!(libc::ETXTBSY); - /// File too large - pub const EFBIG: Errno = errno!(libc::EFBIG); - /// No space left on device - pub const ENOSPC: Errno = errno!(libc::ENOSPC); - /// Illegal seek - pub const ESPIPE: Errno = errno!(libc::ESPIPE); - /// Read-only file system - pub const EROFS: Errno = errno!(libc::EROFS); - /// Too many links - pub const EMLINK: Errno = errno!(libc::EMLINK); - /// Broken pipe - pub const EPIPE: Errno = errno!(libc::EPIPE); - /// Numerical argument out of domain - pub const EDOM: Errno = errno!(libc::EDOM); - /// Numerical result out of range - pub const ERANGE: Errno = errno!(libc::ERANGE); - /// Resource deadlock avoided - pub const EDEADLK: Errno = errno!(libc::EDEADLK); - /// File name too long - pub const ENAMETOOLONG: Errno = errno!(libc::ENAMETOOLONG); - /// No locks available - pub const ENOLCK: Errno = errno!(libc::ENOLCK); - /// Function not implemented - pub const ENOSYS: Errno = errno!(libc::ENOSYS); - /// Directory not empty - pub const ENOTEMPTY: Errno = errno!(libc::ENOTEMPTY); - /// Too many levels of symbolic links - pub const ELOOP: Errno = errno!(libc::ELOOP); - /// Resource temporarily unavailable - pub const EWOULDBLOCK: Errno = errno!(libc::EWOULDBLOCK); - /// No message of desired type - pub const ENOMSG: Errno = errno!(libc::ENOMSG); - /// Identifier removed - pub const EIDRM: Errno = errno!(libc::EIDRM); - /// Object is remote - pub const EREMOTE: Errno = errno!(libc::EREMOTE); - /// Link has been severed - pub const ENOLINK: Errno = errno!(libc::ENOLINK); - /// Protocol error - pub const EPROTO: Errno = errno!(libc::EPROTO); - /// Multihop attempted - pub const EMULTIHOP: Errno = errno!(libc::EMULTIHOP); - /// Bad message - pub const EBADMSG: Errno = errno!(libc::EBADMSG); - /// Value too large for defined data type - pub const EOVERFLOW: Errno = errno!(libc::EOVERFLOW); - /// Invalid or incomplete multibyte or wide character - pub const EILSEQ: Errno = errno!(libc::EILSEQ); - /// Too many users - pub const EUSERS: Errno = errno!(libc::EUSERS); - /// Socket operation on non-socket - pub const ENOTSOCK: Errno = errno!(libc::ENOTSOCK); - /// Destination address required - pub const EDESTADDRREQ: Errno = errno!(libc::EDESTADDRREQ); - /// Message too long - pub const EMSGSIZE: Errno = errno!(libc::EMSGSIZE); - /// Protocol wrong type for socket - pub const EPROTOTYPE: Errno = errno!(libc::EPROTOTYPE); - /// Protocol not available - pub const ENOPROTOOPT: Errno = errno!(libc::ENOPROTOOPT); - /// Protocol not supported - pub const EPROTONOSUPPORT: Errno = errno!(libc::EPROTONOSUPPORT); - /// Socket type not supported - pub const ESOCKTNOSUPPORT: Errno = errno!(libc::ESOCKTNOSUPPORT); - /// Operation not supported - pub const EOPNOTSUPP: Errno = errno!(libc::EOPNOTSUPP); - /// Protocol family not supported - pub const EPFNOSUPPORT: Errno = errno!(libc::EPFNOSUPPORT); - /// Address family not supported by protocol - pub const EAFNOSUPPORT: Errno = errno!(libc::EAFNOSUPPORT); - /// Address already in use - pub const EADDRINUSE: Errno = errno!(libc::EADDRINUSE); - /// Cannot assign requested address - pub const EADDRNOTAVAIL: Errno = errno!(libc::EADDRNOTAVAIL); - /// Network is down - pub const ENETDOWN: Errno = errno!(libc::ENETDOWN); - /// Network is unreachable - pub const ENETUNREACH: Errno = errno!(libc::ENETUNREACH); - /// Network dropped connection on reset - pub const ENETRESET: Errno = errno!(libc::ENETRESET); - /// Software caused connection abort - pub const ECONNABORTED: Errno = errno!(libc::ECONNABORTED); - /// Connection reset by peer - pub const ECONNRESET: Errno = errno!(libc::ECONNRESET); - /// No buffer space available - pub const ENOBUFS: Errno = errno!(libc::ENOBUFS); - /// Transport endpoint is already connected - pub const EISCONN: Errno = errno!(libc::EISCONN); - /// Transport endpoint is not connected - pub const ENOTCONN: Errno = errno!(libc::ENOTCONN); - /// Cannot send after transport endpoint shutdown - pub const ESHUTDOWN: Errno = errno!(libc::ESHUTDOWN); - /// Too many references: cannot splice - pub const ETOOMANYREFS: Errno = errno!(libc::ETOOMANYREFS); - /// Connection timed out - pub const ETIMEDOUT: Errno = errno!(libc::ETIMEDOUT); - /// Connection refused - pub const ECONNREFUSED: Errno = errno!(libc::ECONNREFUSED); - /// Host is down - pub const EHOSTDOWN: Errno = errno!(libc::EHOSTDOWN); - /// No route to host - pub const EHOSTUNREACH: Errno = errno!(libc::EHOSTUNREACH); - /// Operation already in progress - pub const EALREADY: Errno = errno!(libc::EALREADY); - /// Operation now in progress - pub const EINPROGRESS: Errno = errno!(libc::EINPROGRESS); - /// Stale file handle - pub const ESTALE: Errno = errno!(libc::ESTALE); - /// Disk quota exceeded - pub const EDQUOT: Errno = errno!(libc::EDQUOT); - /// Operation cancelled - pub const ECANCELED: Errno = errno!(libc::ECANCELED); - /// Owner died - pub const EOWNERDEAD: Errno = errno!(libc::EOWNERDEAD); - /// State not recoverable - pub const ENOTRECOVERABLE: Errno = errno!(libc::ENOTRECOVERABLE); - /// Operation not supported - pub const ENOTSUP: Errno = errno!(libc::ENOTSUP); - - /// No data available - #[cfg(target_os = "linux")] - pub const ENODATA: Errno = errno!(libc::ENODATA); - #[doc = no_xattr_doc!()] - #[cfg(target_os = "linux")] - pub const NO_XATTR: Errno = Self::ENODATA; - - /// Attribute not found - #[cfg(not(target_os = "linux"))] - pub const ENOATTR: Errno = errno!(libc::ENOATTR); - #[doc = no_xattr_doc!()] - #[cfg(not(target_os = "linux"))] - pub const NO_XATTR: Errno = Self::ENOATTR; - - pub fn from_i32(err: i32) -> Errno { - err.try_into().ok().map_or(Errno::EIO, Errno) - } -} -impl From for Errno { - fn from(err: std::io::Error) -> Self { - let errno = err.raw_os_error().unwrap_or(0); - match errno.try_into() { - Ok(i) => Errno(i), - Err(_) => Errno::EIO, - } - } -} -impl From for Errno { - fn from(x: nix::errno::Errno) -> Self { - let err: std::io::Error = x.into(); - err.into() - } -} -impl From for Errno { - fn from(x: std::io::ErrorKind) -> Self { - let err: std::io::Error = x.into(); - err.into() - } -} -impl From for i32 { - fn from(x: Errno) -> Self { - x.0.into() - } -} - -/// A newtype for generation numbers -/// -/// If the file system will be exported over NFS, the (ino, generation) pairs -/// need to be unique over the file system's lifetime (rather than just the -/// mount time). So if the file system reuses an inode after it has been -/// deleted, it must assign a new, previously unused generation number to the -/// inode at the same time. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Generation(pub u64); -impl From for u64 { - fn from(fh: Generation) -> Self { - fh.0 - } -} - -#[cfg(test)] -mod test { - use std::io::IoSlice; - use std::ops::{Deref, DerefMut}; - /// If we want to be able to cast bytes to our fuse C struct types we need it - /// to be aligned. This struct helps getting &[u8]s which are 8 byte aligned. - #[cfg(test)] - #[repr(align(8))] - pub(crate) struct AlignedData(pub T); - impl Deref for AlignedData { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - impl DerefMut for AlignedData { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - - pub fn ioslice_to_vec(s: &[IoSlice<'_>]) -> Vec { - let mut v = Vec::with_capacity(s.iter().map(|x| x.len()).sum()); - for x in s { - v.extend_from_slice(x); - } - v - } -} diff --git a/cli/src/fuser/mnt/mount_options.rs b/cli/src/fuser/mnt/mount_options.rs deleted file mode 100644 index 220ba60a..00000000 --- a/cli/src/fuser/mnt/mount_options.rs +++ /dev/null @@ -1,239 +0,0 @@ -use std::io; -use std::io::ErrorKind; -use std::{collections::HashSet, ffi::OsStr}; - -/// Mount options accepted by the FUSE filesystem type -/// See 'man mount.fuse' for details -// TODO: add all options that 'man mount.fuse' documents and libfuse supports -#[derive(Debug, Eq, PartialEq, Hash, Clone)] -pub enum MountOption { - /// Set the name of the source in mtab - FSName(String), - /// Set the filesystem subtype in mtab - Subtype(String), - /// Allows passing an option which is not otherwise supported in these enums - #[allow(clippy::upper_case_acronyms)] - CUSTOM(String), - - /* Parameterless options */ - /// Allow all users to access files on this filesystem. By default access is restricted to the - /// user who mounted it - AllowOther, - /// Allow the root user to access this filesystem, in addition to the user who mounted it - AllowRoot, - /// Automatically unmount when the mounting process exits - /// - /// `AutoUnmount` requires `AllowOther` or `AllowRoot`. If `AutoUnmount` is set and neither `Allow...` is set, the FUSE configuration must permit `allow_other`, otherwise mounting will fail. - AutoUnmount, - /// Enable permission checking in the kernel - DefaultPermissions, - - /* Flags */ - /// Enable special character and block devices - Dev, - /// Disable special character and block devices - NoDev, - /// Honor set-user-id and set-groupd-id bits on files - Suid, - /// Don't honor set-user-id and set-groupd-id bits on files - NoSuid, - /// Read-only filesystem - RO, - /// Read-write filesystem - RW, - /// Allow execution of binaries - Exec, - /// Don't allow execution of binaries - NoExec, - /// Support inode access time - Atime, - /// Don't update inode access time - NoAtime, - /// All modifications to directories will be done synchronously - DirSync, - /// All I/O will be done synchronously - Sync, - /// All I/O will be done asynchronously - Async, - /* libfuse library options, such as "direct_io", are not included since they are specific - to libfuse, and not part of the kernel ABI */ -} - -impl MountOption { - pub(crate) fn from_str(s: &str) -> MountOption { - match s { - "auto_unmount" => MountOption::AutoUnmount, - "allow_other" => MountOption::AllowOther, - "allow_root" => MountOption::AllowRoot, - "default_permissions" => MountOption::DefaultPermissions, - "dev" => MountOption::Dev, - "nodev" => MountOption::NoDev, - "suid" => MountOption::Suid, - "nosuid" => MountOption::NoSuid, - "ro" => MountOption::RO, - "rw" => MountOption::RW, - "exec" => MountOption::Exec, - "noexec" => MountOption::NoExec, - "atime" => MountOption::Atime, - "noatime" => MountOption::NoAtime, - "dirsync" => MountOption::DirSync, - "sync" => MountOption::Sync, - "async" => MountOption::Async, - x if x.starts_with("fsname=") => MountOption::FSName(x[7..].into()), - x if x.starts_with("subtype=") => MountOption::Subtype(x[8..].into()), - x => MountOption::CUSTOM(x.into()), - } - } -} - -pub fn check_option_conflicts(options: &[MountOption]) -> Result<(), io::Error> { - let mut options_set = HashSet::new(); - options_set.extend(options.iter().cloned()); - let conflicting: HashSet = options.iter().flat_map(conflicts_with).collect(); - let intersection: Vec = conflicting.intersection(&options_set).cloned().collect(); - if intersection.is_empty() { - Ok(()) - } else { - Err(io::Error::new( - ErrorKind::InvalidInput, - format!("Conflicting mount options found: {intersection:?}"), - )) - } -} - -fn conflicts_with(option: &MountOption) -> Vec { - match option { - MountOption::FSName(_) - | MountOption::Subtype(_) - | MountOption::CUSTOM(_) - | MountOption::DirSync - | MountOption::AutoUnmount - | MountOption::DefaultPermissions => vec![], - MountOption::AllowOther => vec![MountOption::AllowRoot], - MountOption::AllowRoot => vec![MountOption::AllowOther], - MountOption::Dev => vec![MountOption::NoDev], - MountOption::NoDev => vec![MountOption::Dev], - MountOption::Suid => vec![MountOption::NoSuid], - MountOption::NoSuid => vec![MountOption::Suid], - MountOption::RO => vec![MountOption::RW], - MountOption::RW => vec![MountOption::RO], - MountOption::Exec => vec![MountOption::NoExec], - MountOption::NoExec => vec![MountOption::Exec], - MountOption::Atime => vec![MountOption::NoAtime], - MountOption::NoAtime => vec![MountOption::Atime], - MountOption::Sync => vec![MountOption::Async], - MountOption::Async => vec![MountOption::Sync], - } -} - -// Format option to be passed to libfuse or kernel -pub fn option_to_string(option: &MountOption) -> String { - match option { - MountOption::FSName(name) => format!("fsname={name}"), - MountOption::Subtype(subtype) => format!("subtype={subtype}"), - MountOption::CUSTOM(value) => value.to_string(), - MountOption::AutoUnmount => "auto_unmount".to_string(), - MountOption::AllowRoot | - // AllowRoot is implemented by allowing everyone access and then restricting to - // root + owner within fuser - MountOption::AllowOther => "allow_other".to_string(), - MountOption::DefaultPermissions => "default_permissions".to_string(), - MountOption::Dev => "dev".to_string(), - MountOption::NoDev => "nodev".to_string(), - MountOption::Suid => "suid".to_string(), - MountOption::NoSuid => "nosuid".to_string(), - MountOption::RO => "ro".to_string(), - MountOption::RW => "rw".to_string(), - MountOption::Exec => "exec".to_string(), - MountOption::NoExec => "noexec".to_string(), - MountOption::Atime => "atime".to_string(), - MountOption::NoAtime => "noatime".to_string(), - MountOption::DirSync => "dirsync".to_string(), - MountOption::Sync => "sync".to_string(), - MountOption::Async => "async".to_string(), - } -} - -/// Parses mount command args. -/// -/// Input: `"-o", "suid", "-o", "ro,nodev,noexec", "-osync"` -/// Output Ok([`Suid`, `RO`, `NoDev`, `NoExec`, `Sync`]) -pub(crate) fn parse_options_from_args(args: &[&OsStr]) -> io::Result> { - let err = |x| io::Error::new(ErrorKind::InvalidInput, x); - let args: Option> = args.iter().map(|x| x.to_str()).collect(); - let args = args.ok_or_else(|| err("Error parsing args: Invalid UTF-8".to_owned()))?; - let mut it = args.iter(); - let mut out = vec![]; - loop { - let opt = match it.next() { - None => break, - Some(&"-o") => *it.next().ok_or_else(|| { - err("Error parsing args: Expected option, reached end of args".to_owned()) - })?, - Some(x) if x.starts_with("-o") => &x[2..], - Some(x) => return Err(err(format!("Error parsing args: expected -o, got {x}"))), - }; - for x in opt.split(',') { - out.push(MountOption::from_str(x)); - } - } - Ok(out) -} - -#[cfg(test)] -mod test { - use std::os::unix::prelude::OsStrExt; - - use super::*; - - #[test] - fn option_checking() { - assert!(check_option_conflicts(&[MountOption::Suid, MountOption::NoSuid]).is_err()); - assert!(check_option_conflicts(&[MountOption::Suid, MountOption::NoExec]).is_ok()); - } - #[test] - fn option_round_trip() { - use super::MountOption::*; - for x in &[ - FSName("Blah".to_owned()), - Subtype("Bloo".to_owned()), - CUSTOM("bongos".to_owned()), - AllowOther, - AutoUnmount, - DefaultPermissions, - Dev, - NoDev, - Suid, - NoSuid, - RO, - RW, - Exec, - NoExec, - Atime, - NoAtime, - DirSync, - Sync, - Async, - ] { - assert_eq!(*x, MountOption::from_str(option_to_string(x).as_ref())); - } - } - - #[test] - fn test_parse_options() { - use super::MountOption::*; - - assert_eq!(parse_options_from_args(&[]).unwrap(), &[]); - - let o: Vec<_> = "-o suid -o ro,nodev,noexec -osync" - .split(' ') - .map(OsStr::new) - .collect(); - let out = parse_options_from_args(o.as_ref()).unwrap(); - assert_eq!(out, [Suid, RO, NoDev, NoExec, Sync]); - - assert!(parse_options_from_args(&[OsStr::new("-o")]).is_err()); - assert!(parse_options_from_args(&[OsStr::new("not o")]).is_err()); - assert!(parse_options_from_args(&[OsStr::from_bytes(b"-o\xc3\x28")]).is_err()); - } -} diff --git a/cli/src/fuser/notify.rs b/cli/src/fuser/notify.rs deleted file mode 100644 index 7d939877..00000000 --- a/cli/src/fuser/notify.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::io; - -#[allow(unused)] -use std::{convert::TryInto, ffi::OsStr}; - -use super::{ - channel::ChannelSender, - ll::{fuse_abi::fuse_notify_code as notify_code, notify::Notification}, - - // What we're sending here aren't really replies, but they - // move in the same direction (userspace->kernel), so we can - // reuse ReplySender for it. - reply::ReplySender, -}; - -/// A handle to a pending `poll()` request. Can be saved and used to notify the -/// kernel when a poll is ready. -#[derive(Clone)] -pub struct PollHandle { - handle: u64, - notifier: Notifier, -} - -impl PollHandle { - pub(crate) fn new(cs: ChannelSender, kh: u64) -> Self { - Self { - handle: kh, - notifier: Notifier::new(cs), - } - } - - /// Notify the kernel that the associated file handle is ready to be polled. - /// # Errors - /// Returns an error if the kernel rejects the notification. - pub fn notify(self) -> io::Result<()> { - self.notifier.poll(self.handle) - } -} - -impl From for u64 { - fn from(value: PollHandle) -> Self { - value.handle - } -} - -impl std::fmt::Debug for PollHandle { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("PollHandle").field(&self.handle).finish() - } -} - -/// A handle by which the application can send notifications to the server -#[derive(Debug, Clone)] -pub struct Notifier(ChannelSender); - -impl Notifier { - pub(crate) fn new(cs: ChannelSender) -> Self { - Self(cs) - } - - /// Notify poll clients of I/O readiness - /// # Errors - /// Returns an error if the kernel rejects the notification. - pub fn poll(&self, kh: u64) -> io::Result<()> { - let notif = Notification::new_poll(kh); - self.send(notify_code::FUSE_POLL, ¬if) - } - - /// Invalidate the kernel cache for a given directory entry - /// # Errors - /// Returns an error if the notification data is too large. - /// Returns an error if the kernel rejects the notification. - pub fn inval_entry(&self, parent: u64, name: &OsStr) -> io::Result<()> { - let notif = Notification::new_inval_entry(parent, name).map_err(Self::too_big_err)?; - self.send_inval(notify_code::FUSE_NOTIFY_INVAL_ENTRY, ¬if) - } - - /// Invalidate the kernel cache for a given inode (metadata and - /// data in the given range) - /// # Errors - /// Returns an error if the kernel rejects the notification. - pub fn inval_inode(&self, ino: u64, offset: i64, len: i64) -> io::Result<()> { - let notif = Notification::new_inval_inode(ino, offset, len); - self.send_inval(notify_code::FUSE_NOTIFY_INVAL_INODE, ¬if) - } - - /// Update the kernel's cached copy of a given inode's data - /// # Errors - /// Returns an error if the notification data is too large. - /// Returns an error if the kernel rejects the notification. - pub fn store(&self, ino: u64, offset: u64, data: &[u8]) -> io::Result<()> { - let notif = Notification::new_store(ino, offset, data).map_err(Self::too_big_err)?; - // Not strictly an invalidate, but the inode we're operating - // on may have been evicted anyway, so treat is as such - self.send_inval(notify_code::FUSE_NOTIFY_STORE, ¬if) - } - - /// Invalidate the kernel cache for a given directory entry and inform - /// inotify watchers of a file deletion. - /// # Errors - /// Returns an error if the notification data is too large. - /// Returns an error if the kernel rejects the notification. - pub fn delete(&self, parent: u64, child: u64, name: &OsStr) -> io::Result<()> { - let notif = Notification::new_delete(parent, child, name).map_err(Self::too_big_err)?; - self.send_inval(notify_code::FUSE_NOTIFY_DELETE, ¬if) - } - - #[allow(unused)] - fn send_inval(&self, code: notify_code, notification: &Notification<'_>) -> io::Result<()> { - match self.send(code, notification) { - // ENOENT is harmless for an invalidation (the - // kernel may have already dropped the cached - // entry on its own anyway), so ignore it. - Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()), - x => x, - } - } - - fn send(&self, code: notify_code, notification: &Notification<'_>) -> io::Result<()> { - notification - .with_iovec(code, |iov| self.0.send(iov)) - .map_err(Self::too_big_err)? - } - - /// Create an error for indicating when a notification message - /// would exceed the capacity that its length descriptor field is - /// capable of encoding. - fn too_big_err(tfie: std::num::TryFromIntError) -> io::Error { - io::Error::new(io::ErrorKind::Other, format!("Data too large: {tfie:?}")) - } -} diff --git a/cli/src/lib.rs b/cli/src/lib.rs deleted file mode 100644 index dffc0706..00000000 --- a/cli/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -pub mod cmd; -pub mod opts; -pub mod sandbox; - -#[cfg(target_os = "linux")] -pub mod daemon; - -#[cfg(target_os = "linux")] -pub mod fuse; - -#[cfg(target_os = "linux")] -pub mod fuser; - -#[cfg(unix)] -pub mod nfsserve; - -#[cfg(unix)] -pub mod nfs; - -#[cfg(unix)] -pub mod mount; - -pub fn get_runtime() -> tokio::runtime::Runtime { - tokio::runtime::Runtime::new().expect("Internal error: failed to initialize runtime") -} diff --git a/cli/src/main.rs b/cli/src/main.rs deleted file mode 100644 index 23b48d59..00000000 --- a/cli/src/main.rs +++ /dev/null @@ -1,493 +0,0 @@ -use agentfs::{ - cmd::{self, completions::handle_completions}, - get_runtime, - opts::{Args, Command, FsCommand, PruneCommand, ServeCommand, SyncCommand}, -}; -use clap::{CommandFactory, Parser}; -use clap_complete::CompleteEnv; -use tracing_subscriber::prelude::*; - -/// Parse and validate encryption key and cipher options. -/// Both must be provided together or neither. -fn parse_encryption(key: Option, cipher: Option) -> Option<(String, String)> { - match (key, cipher) { - (Some(key), Some(cipher)) => Some((key, cipher)), - (Some(_), None) => { - eprintln!("Error: --cipher is required when using --key"); - std::process::exit(1); - } - (None, Some(_)) => { - eprintln!("Error: --key is required when using --cipher"); - std::process::exit(1); - } - (None, None) => None, - } -} - -fn partial_origin_policy( - mode: Option, - threshold_bytes: Option, -) -> Option { - match (mode, threshold_bytes) { - (None, None) => None, - (Some(mode), threshold_bytes) => { - let mut policy = agentfs_sdk::PartialOriginPolicy::new(mode.into()); - if let Some(threshold_bytes) = threshold_bytes { - policy = policy.with_threshold_bytes(threshold_bytes); - } - Some(policy) - } - (None, Some(threshold_bytes)) => Some( - agentfs_sdk::PartialOriginPolicy::new(agentfs_sdk::PartialOriginMode::Auto) - .with_threshold_bytes(threshold_bytes), - ), - } -} - -fn main() { - let _ = tracing_subscriber::registry() - .with(tracing_subscriber::fmt::layer()) - .with( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| "agentfs=info".into()), - ) - .try_init(); - - reset_sigpipe(); - - CompleteEnv::with_factory(Args::command).complete(); - let args = Args::parse(); - - match args.command { - Command::Init { - id, - force, - base, - key, - cipher, - command, - backend, - sync, - } => { - let rt = get_runtime(); - let encryption_opts = parse_encryption(key, cipher) - .map(|(key, cipher)| cmd::init::EncryptionOptions { key, cipher }); - if let Err(e) = rt.block_on(cmd::init::init_database( - id, - sync, - force, - base, - encryption_opts, - command, - backend, - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - Command::Sync { - id_or_path, - command, - } => match command { - SyncCommand::Pull => { - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::sync::handle_pull_command(id_or_path)) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - SyncCommand::Push => { - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::sync::handle_push_command(id_or_path)) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - SyncCommand::Checkpoint => { - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::sync::handle_checkpoint_command(id_or_path)) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - SyncCommand::Stats => { - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::sync::handle_stats_command( - &mut std::io::stdout(), - id_or_path, - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - }, - Command::Run { - allow, - no_default_allows, - experimental_sandbox, - strace, - session, - system, - partial_origin, - partial_origin_threshold_bytes, - key, - cipher, - command, - args, - } => { - let encryption = parse_encryption(key, cipher); - let partial_origin_policy = - partial_origin_policy(partial_origin, partial_origin_threshold_bytes); - let command = command.unwrap_or_else(default_shell); - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::handle_run_command( - allow, - no_default_allows, - experimental_sandbox, - strace, - session, - system, - encryption, - partial_origin_policy, - command, - args, - )) { - eprintln!("Error: {e:?}"); - std::process::exit(1); - } - } - #[cfg(unix)] - Command::Exec { - id_or_path, - command, - args, - backend, - key, - cipher, - } => { - let encryption = parse_encryption(key, cipher); - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::exec::handle_exec_command( - id_or_path, command, args, backend, encryption, - )) { - eprintln!("Error: {e:?}"); - std::process::exit(1); - } - } - #[cfg(unix)] - Command::Clone { - id_or_path, - source, - name, - backend, - verify, - } => { - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::clone::handle_clone_command( - id_or_path, source, name, backend, verify, - )) { - eprintln!("Error: {e:?}"); - std::process::exit(1); - } - } - Command::Mount { - id_or_path, - mountpoint, - auto_unmount, - allow_root, - system, - foreground, - uid, - gid, - backend, - partial_origin, - partial_origin_threshold_bytes, - } => match (id_or_path, mountpoint) { - (Some(id_or_path), Some(mountpoint)) => { - if let Err(e) = cmd::mount(cmd::MountArgs { - id_or_path, - mountpoint, - auto_unmount, - allow_root, - allow_other: system, - foreground, - uid, - gid, - backend, - partial_origin_policy: partial_origin_policy( - partial_origin, - partial_origin_threshold_bytes, - ), - }) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - (None, None) => { - cmd::mount::list_mounts(&mut std::io::stdout()); - } - _ => { - eprintln!("Error: both ID_OR_PATH and MOUNTPOINT are required to mount"); - std::process::exit(1); - } - }, - Command::Diff { id_or_path } => { - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::fs::diff_filesystem(id_or_path)) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - Command::Timeline { - id_or_path, - limit, - filter, - status, - format, - } => { - let rt = get_runtime(); - let options = cmd::timeline::TimelineOptions { - limit, - filter, - status, - format, - }; - if let Err(e) = rt.block_on(cmd::timeline::show_timeline( - &mut std::io::stdout(), - &id_or_path, - &options, - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - Command::Fs { - command, - id_or_path, - key, - cipher, - } => { - let encryption = parse_encryption(key, cipher); - let rt = get_runtime(); - match command { - FsCommand::Ls { fs_path } => { - if let Err(e) = rt.block_on(cmd::fs::ls_filesystem( - &mut std::io::stdout(), - id_or_path, - &fs_path, - encryption.as_ref(), - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - FsCommand::Cat { file_path } => { - if let Err(e) = rt.block_on(cmd::fs::cat_filesystem( - &mut std::io::stdout(), - id_or_path, - &file_path, - encryption.as_ref(), - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - FsCommand::Write { file_path, content } => { - if let Err(e) = rt.block_on(cmd::fs::write_filesystem( - id_or_path, - &file_path, - &content, - encryption.as_ref(), - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - } - } - Command::Completions { command } => handle_completions(command), - #[cfg(unix)] - Command::Nfs { - id_or_path, - bind, - port, - } => { - eprintln!("Warning: `agentfs nfs` is deprecated, use `agentfs serve nfs` instead"); - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::nfs::handle_nfs_command(id_or_path, bind, port)) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - Command::McpServer { id_or_path, tools } => { - eprintln!( - "Warning: `agentfs mcp-server` is deprecated, use `agentfs serve mcp` instead" - ); - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::mcp_server::handle_mcp_server_command( - id_or_path, tools, - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - Command::Serve { command } => match command { - #[cfg(unix)] - ServeCommand::Nfs { - id_or_path, - bind, - port, - } => { - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::nfs::handle_nfs_command(id_or_path, bind, port)) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - ServeCommand::Mcp { id_or_path, tools } => { - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::mcp_server::handle_mcp_server_command( - id_or_path, tools, - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - }, - Command::Ps => { - if let Err(e) = cmd::ps::list_ps(&mut std::io::stdout()) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - Command::Prune { command } => match command { - PruneCommand::Mounts { force } => { - if let Err(e) = cmd::mount::prune_mounts(force) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - }, - Command::Integrity { - id_or_path, - json, - require_portable, - check_base, - key, - cipher, - } => { - let encryption = parse_encryption(key, cipher); - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::safety::handle_integrity_command( - &mut std::io::stdout(), - id_or_path, - json, - require_portable, - check_base, - encryption.as_ref(), - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - Command::Backup { - id_or_path, - target, - verify, - materialize, - key, - cipher, - } => { - let encryption = parse_encryption(key, cipher); - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::safety::handle_backup_command( - &mut std::io::stdout(), - id_or_path, - target, - verify, - materialize, - encryption.as_ref(), - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - Command::Materialize { - id_or_path, - output, - verify, - key, - cipher, - } => { - let encryption = parse_encryption(key, cipher); - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::safety::handle_materialize_command( - &mut std::io::stdout(), - id_or_path, - output, - verify, - encryption.as_ref(), - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - Command::Migrate { - id_or_path, - dry_run, - } => { - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::migrate::handle_migrate_command( - &mut std::io::stdout(), - id_or_path, - dry_run, - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - Command::MigrateV0_5 { - source, - target, - verify, - overwrite_target, - } => { - let rt = get_runtime(); - if let Err(e) = rt.block_on(cmd::migrate::handle_migrate_v0_5_command( - &mut std::io::stdout(), - source, - target, - verify, - overwrite_target, - )) { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } - } -} - -/// Reset SIGPIPE to the default behavior (terminate the process) so that -/// piping output to tools like `head` doesn't cause a panic. -#[cfg(unix)] -fn reset_sigpipe() { - unsafe { - libc::signal(libc::SIGPIPE, libc::SIG_DFL); - } -} - -#[cfg(not(unix))] -fn reset_sigpipe() {} - -/// Returns the default shell for the current platform. -/// Linux uses bash, macOS uses zsh. -fn default_shell() -> std::path::PathBuf { - #[cfg(target_os = "macos")] - { - std::path::PathBuf::from("zsh") - } - #[cfg(not(target_os = "macos"))] - { - std::path::PathBuf::from("bash") - } -} diff --git a/cli/src/mount/mod.rs b/cli/src/mount/mod.rs deleted file mode 100644 index 68d0122a..00000000 --- a/cli/src/mount/mod.rs +++ /dev/null @@ -1,261 +0,0 @@ -//! Generic mount infrastructure for AgentFS. -//! -//! This module provides a unified mount API that abstracts over FUSE and NFS backends. -//! The `mount_fs()` function returns a `MountHandle` that automatically unmounts when dropped. -//! -//! # Example -//! -//! ```ignore -//! use agentfs_cli::mount::{mount_fs, MountOpts, MountBackend}; -//! -//! let opts = MountOpts::new(PathBuf::from("/mnt/agent"), MountBackend::Fuse); -//! let handle = mount_fs(Arc::new(my_fs), opts).await?; -//! // ... use the mounted filesystem ... -//! drop(handle); // auto-unmounts -//! ``` - -#[cfg(target_os = "linux")] -mod fuse; -mod nfs; - -use anyhow::Result; -use std::path::{Path, PathBuf}; -use std::sync::Arc; -use std::time::Duration; -use tokio_util::sync::CancellationToken; - -pub use crate::opts::MountBackend; - -/// Default timeout for mount to become ready. -const DEFAULT_MOUNT_TIMEOUT: Duration = Duration::from_secs(10); - -/// Options for mounting a filesystem. -/// -/// This struct provides a unified configuration for both FUSE and NFS backends. -/// Use `MountOpts::new()` to create default options, then customize as needed. -#[derive(Debug, Clone)] -pub struct MountOpts { - /// The mountpoint path. - pub mountpoint: PathBuf, - /// Mount backend to use. - pub backend: MountBackend, - /// Filesystem name shown in mount output. - pub fsname: String, - /// User ID to report for all files. - pub uid: Option, - /// Group ID to report for all files. - pub gid: Option, - /// Allow other system users to access the mount. - pub allow_other: bool, - /// Allow root to access the mount (FUSE only). - pub allow_root: bool, - /// Auto unmount when process exits (FUSE only). - pub auto_unmount: bool, - /// Use lazy unmount on cleanup. - pub lazy_unmount: bool, - /// Timeout for mount to become ready. - pub timeout: Duration, -} - -impl MountOpts { - /// Create default options for the given mountpoint and backend. - pub fn new(mountpoint: PathBuf, backend: MountBackend) -> Self { - Self { - mountpoint, - backend, - fsname: "agentfs".to_string(), - uid: None, - gid: None, - allow_other: false, - allow_root: false, - auto_unmount: false, - lazy_unmount: false, - timeout: DEFAULT_MOUNT_TIMEOUT, - } - } -} - -impl Default for MountOpts { - fn default() -> Self { - Self::new(PathBuf::new(), MountBackend::default()) - } -} - -/// A mounted filesystem handle. Automatically unmounts when dropped. -/// -/// This handle represents an active mount and provides RAII-style cleanup. -/// When the handle is dropped, the filesystem is automatically unmounted. -pub struct MountHandle { - mountpoint: PathBuf, - backend: MountBackend, - lazy_unmount: bool, - inner: MountHandleInner, -} - -pub(crate) enum MountHandleInner { - #[cfg(target_os = "linux")] - Fuse { - thread: Option>>, - }, - Nfs { - shutdown: CancellationToken, - _server_handle: tokio::task::JoinHandle<()>, - }, -} - -impl MountHandle { - /// Get the mountpoint path. - pub fn mountpoint(&self) -> &Path { - &self.mountpoint - } -} - -impl Drop for MountHandle { - fn drop(&mut self) { - // Move away from mountpoint before unmounting to avoid EBUSY - let _ = std::env::set_current_dir("/"); - - match &mut self.inner { - #[cfg(target_os = "linux")] - MountHandleInner::Fuse { thread } => { - if let Err(e) = unmount(&self.mountpoint, self.backend, self.lazy_unmount) { - eprintln!( - "Warning: Failed to unmount FUSE filesystem at {}: {}", - self.mountpoint.display(), - e - ); - } - if let Some(thread) = thread.take() { - match thread.join() { - Ok(Ok(())) => {} - Ok(Err(e)) => eprintln!("Warning: FUSE session exited with error: {e}"), - Err(e) => eprintln!("Warning: FUSE session thread panicked: {e:?}"), - } - } - } - MountHandleInner::Nfs { shutdown, .. } => { - // Signal the NFS server to shut down - shutdown.cancel(); - - // Unmount the NFS filesystem - if let Err(e) = unmount(&self.mountpoint, self.backend, self.lazy_unmount) { - eprintln!( - "Warning: Failed to unmount NFS filesystem at {}: {}", - self.mountpoint.display(), - e - ); - } - } - } - } -} - -/// Unmount a filesystem at the given mountpoint. -/// -/// This function handles unmounting for both FUSE and NFS backends. -/// If `lazy` is true, uses lazy unmount which detaches immediately even if busy. -pub fn unmount(mountpoint: &Path, backend: MountBackend, lazy: bool) -> Result<()> { - match backend { - #[cfg(target_os = "linux")] - MountBackend::Fuse => fuse::unmount_fuse(mountpoint, lazy), - #[cfg(not(target_os = "linux"))] - MountBackend::Fuse => anyhow::bail!("FUSE is not supported on this platform"), - MountBackend::Nfs => nfs::unmount_nfs(mountpoint, lazy), - } -} - -/// Mount a filesystem with the given options. -/// -/// Returns a handle that automatically unmounts when dropped. -/// The filesystem must be wrapped in `Arc`. -#[cfg(target_os = "linux")] -pub async fn mount_fs( - fs: Arc, - opts: MountOpts, -) -> Result { - match opts.backend { - MountBackend::Fuse => fuse::mount_fuse(fs, opts), - MountBackend::Nfs => nfs::mount_nfs(fs, opts).await, - } -} - -/// Mount a filesystem with the given options (macOS version). -#[cfg(target_os = "macos")] -pub async fn mount_fs( - fs: Arc, - opts: MountOpts, -) -> Result { - match opts.backend { - MountBackend::Fuse => { - anyhow::bail!( - "FUSE mounting is not supported on macOS.\n\ - Use --backend nfs (default) instead." - ); - } - MountBackend::Nfs => nfs::mount_nfs(fs, opts).await, - } -} - -/// Resolve when SIGTERM, SIGINT, or SIGHUP is delivered. -/// -/// Mount-owning commands must tear down through this rather than the default -/// signal disposition: dying without unmounting leaves a dead mount table -/// entry (ENOTCONN for every later visitor) and skips `MountHandle`'s Drop. -#[cfg(unix)] -pub async fn shutdown_signal() -> std::io::Result<()> { - use tokio::signal::unix::{signal, SignalKind}; - let mut term = signal(SignalKind::terminate())?; - let mut int = signal(SignalKind::interrupt())?; - let mut hup = signal(SignalKind::hangup())?; - tokio::select! { - _ = term.recv() => (), - _ = int.recv() => (), - _ = hup.recv() => (), - } - Ok(()) -} - -/// Wait for a path to become a mountpoint. -pub fn wait_for_mount(path: &Path, timeout: Duration) -> bool { - let start = std::time::Instant::now(); - let interval = Duration::from_millis(50); - - while start.elapsed() < timeout { - if is_mountpoint(path) { - return true; - } - std::thread::sleep(interval); - } - false -} - -/// Check if a path is a mountpoint by comparing device IDs with parent. -pub fn is_mountpoint(path: &Path) -> bool { - #[cfg(unix)] - { - use std::os::unix::fs::MetadataExt; - - let path_meta = match std::fs::metadata(path) { - Ok(m) => m, - Err(_) => return false, - }; - - let parent = match path.parent() { - Some(p) if !p.as_os_str().is_empty() => p, - _ => Path::new("/"), - }; - - let parent_meta = match std::fs::metadata(parent) { - Ok(m) => m, - Err(_) => return false, - }; - - path_meta.dev() != parent_meta.dev() - } - - #[cfg(not(unix))] - { - let _ = path; - false - } -} diff --git a/cli/src/nfsserve/config.rs b/cli/src/nfsserve/config.rs deleted file mode 100644 index 8b137891..00000000 --- a/cli/src/nfsserve/config.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/cli/src/nfsserve/fs_util.rs b/cli/src/nfsserve/fs_util.rs deleted file mode 100644 index 21361039..00000000 --- a/cli/src/nfsserve/fs_util.rs +++ /dev/null @@ -1,181 +0,0 @@ -use super::nfs::*; -use std::fs::Metadata; -use std::fs::Permissions; - -#[cfg(unix)] -use std::os::unix::fs::{MetadataExt, PermissionsExt}; -use std::path::Path; -use tokio::fs::OpenOptions; -use tracing::debug; - -/// Compares if file metadata has changed in a significant way -#[cfg(any(target_os = "linux", target_os = "macos"))] -pub fn metadata_differ(lhs: &Metadata, rhs: &Metadata) -> bool { - lhs.ino() != rhs.ino() - || lhs.mtime() != rhs.mtime() - || lhs.len() != rhs.len() - || lhs.file_type() != rhs.file_type() -} -pub fn fattr3_differ(lhs: &fattr3, rhs: &fattr3) -> bool { - lhs.fileid != rhs.fileid - || lhs.mtime.seconds != rhs.mtime.seconds - || lhs.mtime.nseconds != rhs.mtime.nseconds - || lhs.size != rhs.size - || lhs.ftype as u32 != rhs.ftype as u32 -} - -/// path.exists() is terrifyingly unsafe as that -/// traverses symlinks. This can cause deadlocks if we have a -/// recursive symlink. -pub fn exists_no_traverse(path: &Path) -> bool { - path.symlink_metadata().is_ok() -} - -fn mode_unmask(mode: u32) -> u32 { - // it is possible to create a file we cannot write to. - // we force writable always. - let mode = mode | 0x80; - let mode = Permissions::from_mode(mode); - mode.mode() & 0x1FF -} - -/// Converts fs Metadata to NFS fattr3 -pub fn metadata_to_fattr3(fid: fileid3, meta: &Metadata) -> fattr3 { - let size = meta.size(); - let file_mode = mode_unmask(meta.mode()); - if meta.is_file() { - fattr3 { - ftype: ftype3::NF3REG, - mode: file_mode, - nlink: 1, - uid: meta.uid(), - gid: meta.gid(), - size, - used: size, - rdev: specdata3::default(), - fsid: 0, - fileid: fid, - atime: nfstime3 { - seconds: meta.atime() as u32, - nseconds: meta.atime_nsec() as u32, - }, - mtime: nfstime3 { - seconds: meta.mtime() as u32, - nseconds: meta.mtime_nsec() as u32, - }, - ctime: nfstime3 { - seconds: meta.ctime() as u32, - nseconds: meta.ctime_nsec() as u32, - }, - } - } else if meta.is_symlink() { - fattr3 { - ftype: ftype3::NF3LNK, - mode: file_mode, - nlink: 1, - uid: meta.uid(), - gid: meta.gid(), - size, - used: size, - rdev: specdata3::default(), - fsid: 0, - fileid: fid, - atime: nfstime3 { - seconds: meta.atime() as u32, - nseconds: meta.atime_nsec() as u32, - }, - mtime: nfstime3 { - seconds: meta.mtime() as u32, - nseconds: meta.mtime_nsec() as u32, - }, - ctime: nfstime3 { - seconds: meta.ctime() as u32, - nseconds: meta.ctime_nsec() as u32, - }, - } - } else { - fattr3 { - ftype: ftype3::NF3DIR, - mode: file_mode, - nlink: 2, - uid: meta.uid(), - gid: meta.gid(), - size, - used: size, - rdev: specdata3::default(), - fsid: 0, - fileid: fid, - atime: nfstime3 { - seconds: meta.atime() as u32, - nseconds: meta.atime_nsec() as u32, - }, - mtime: nfstime3 { - seconds: meta.mtime() as u32, - nseconds: meta.mtime_nsec() as u32, - }, - ctime: nfstime3 { - seconds: meta.ctime() as u32, - nseconds: meta.ctime_nsec() as u32, - }, - } - } -} - -/// Set attributes of a path -pub async fn path_setattr(path: &Path, setattr: &sattr3) -> Result<(), nfsstat3> { - match setattr.atime { - set_atime::SET_TO_SERVER_TIME => { - let _ = filetime::set_file_atime(path, filetime::FileTime::now()); - } - set_atime::SET_TO_CLIENT_TIME(time) => { - let _ = filetime::set_file_atime(path, time.into()); - } - _ => {} - }; - match setattr.mtime { - set_mtime::SET_TO_SERVER_TIME => { - let _ = filetime::set_file_mtime(path, filetime::FileTime::now()); - } - set_mtime::SET_TO_CLIENT_TIME(time) => { - let _ = filetime::set_file_mtime(path, time.into()); - } - _ => {} - }; - if let set_mode3::mode(mode) = setattr.mode { - debug!(" -- set permissions {:?} {:?}", path, mode); - let mode = mode_unmask(mode); - let _ = std::fs::set_permissions(path, Permissions::from_mode(mode)); - }; - if let set_uid3::uid(_) = setattr.uid { - debug!("Set uid not implemented"); - } - if let set_gid3::gid(_) = setattr.gid { - debug!("Set gid not implemented"); - } - if let set_size3::size(size3) = setattr.size { - let file = OpenOptions::new() - .read(true) - .write(true) - .truncate(false) - .open(path) - .await - .or(Err(nfsstat3::NFS3ERR_IO))?; - debug!(" -- set size {:?} {:?}", path, size3); - file.set_len(size3).await.or(Err(nfsstat3::NFS3ERR_IO))?; - } - Ok(()) -} - -/// Set attributes of a file -pub async fn file_setattr(file: &std::fs::File, setattr: &sattr3) -> Result<(), nfsstat3> { - if let set_mode3::mode(mode) = setattr.mode { - debug!(" -- set permissions {:?}", mode); - let mode = mode_unmask(mode); - let _ = file.set_permissions(Permissions::from_mode(mode)); - } - if let set_size3::size(size3) = setattr.size { - debug!(" -- set size {:?}", size3); - file.set_len(size3).or(Err(nfsstat3::NFS3ERR_IO))?; - } - Ok(()) -} diff --git a/cli/src/nfsserve/mod.rs b/cli/src/nfsserve/mod.rs deleted file mode 100644 index 7c62d912..00000000 --- a/cli/src/nfsserve/mod.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![cfg_attr(feature = "strict", deny(warnings))] - -mod context; -pub mod permissions; -pub mod rpc; -mod rpcwire; -mod write_counter; -pub mod xdr; - -mod mount; -mod mount_handlers; - -mod portmap; -mod portmap_handlers; - -pub mod nfs; -mod nfs_handlers; - -#[cfg(not(target_os = "windows"))] -pub mod fs_util; - -pub mod tcp; -mod transaction_tracker; -pub mod vfs; diff --git a/cli/src/nfsserve/permissions.rs b/cli/src/nfsserve/permissions.rs deleted file mode 100644 index 15280b41..00000000 --- a/cli/src/nfsserve/permissions.rs +++ /dev/null @@ -1,308 +0,0 @@ -//! Unix permission checking for NFS operations. -//! -//! This module implements RFC 1813 compliant permission checking using -//! AUTH_UNIX credentials (uid, gid, auxiliary gids) against file mode bits. - -use super::nfs::fattr3; -use super::rpc::auth_unix; - -/// Permission bits for Unix file modes -pub const S_IRUSR: u32 = 0o400; // Owner read -pub const S_IWUSR: u32 = 0o200; // Owner write -pub const S_IXUSR: u32 = 0o100; // Owner execute -pub const S_IRGRP: u32 = 0o040; // Group read -pub const S_IWGRP: u32 = 0o020; // Group write -pub const S_IXGRP: u32 = 0o010; // Group execute -pub const S_IROTH: u32 = 0o004; // Other read -pub const S_IWOTH: u32 = 0o002; // Other write -pub const S_IXOTH: u32 = 0o001; // Other execute -pub const S_ISVTX: u32 = 0o1000; // Sticky bit - -/// NFS ACCESS procedure permission bits (from RFC 1813) -pub const ACCESS3_READ: u32 = 0x0001; -pub const ACCESS3_LOOKUP: u32 = 0x0002; -pub const ACCESS3_MODIFY: u32 = 0x0004; -pub const ACCESS3_EXTEND: u32 = 0x0008; -pub const ACCESS3_DELETE: u32 = 0x0010; -pub const ACCESS3_EXECUTE: u32 = 0x0020; - -/// Permission type for checking -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Permission { - Read, - Write, - Execute, -} - -/// Check if the given auth credentials have the specified permission on the file. -/// -/// This implements standard Unix permission checking: -/// 1. Root (uid 0) always has all permissions -/// 2. If caller's uid matches file owner, check owner bits -/// 3. If caller's gid or any auxiliary gid matches file group, check group bits -/// 4. Otherwise check "other" bits -pub fn check_permission(auth: &auth_unix, attr: &fattr3, perm: Permission) -> bool { - let mode = attr.mode; - let file_uid = attr.uid; - let file_gid = attr.gid; - - // Root always has access - if auth.uid == 0 { - return true; - } - - // Determine which permission bits to check based on user/group/other - let (read_bit, write_bit, exec_bit) = if auth.uid == file_uid { - // Owner permissions - (S_IRUSR, S_IWUSR, S_IXUSR) - } else if is_in_group(auth, file_gid) { - // Group permissions - (S_IRGRP, S_IWGRP, S_IXGRP) - } else { - // Other permissions - (S_IROTH, S_IWOTH, S_IXOTH) - }; - - // Check the specific permission - match perm { - Permission::Read => (mode & read_bit) != 0, - Permission::Write => (mode & write_bit) != 0, - Permission::Execute => (mode & exec_bit) != 0, - } -} - -/// Check if auth credentials are in the specified group. -/// Returns true if the primary gid matches or if gid is in auxiliary groups. -fn is_in_group(auth: &auth_unix, gid: u32) -> bool { - if auth.gid == gid { - return true; - } - auth.gids.contains(&gid) -} - -/// Check if the caller can read the file. -pub fn can_read(auth: &auth_unix, attr: &fattr3) -> bool { - check_permission(auth, attr, Permission::Read) -} - -/// Check if the caller can write to the file. -pub fn can_write(auth: &auth_unix, attr: &fattr3) -> bool { - check_permission(auth, attr, Permission::Write) -} - -/// Check if the caller can execute the file or search the directory. -pub fn can_execute(auth: &auth_unix, attr: &fattr3) -> bool { - check_permission(auth, attr, Permission::Execute) -} - -/// Compute the ACCESS3 result bitmask for the given auth and file attributes. -/// -/// This implements RFC 1813 ACCESS procedure semantics: -/// - ACCESS3_READ: read file data or directory contents -/// - ACCESS3_LOOKUP: search directory entries (execute permission on directories) -/// - ACCESS3_MODIFY: alter existing file/directory data -/// - ACCESS3_EXTEND: add new data or directory entries -/// - ACCESS3_DELETE: remove directory entries (checked against parent directory) -/// - ACCESS3_EXECUTE: execute files (execute permission on files) -pub fn compute_access(auth: &auth_unix, attr: &fattr3, requested: u32) -> u32 { - let mut result = 0u32; - let is_dir = matches!(attr.ftype, super::nfs::ftype3::NF3DIR); - - // ACCESS3_READ - read file data or directory contents - if (requested & ACCESS3_READ) != 0 && can_read(auth, attr) { - result |= ACCESS3_READ; - } - - // ACCESS3_LOOKUP - search directory (execute permission on directories) - if (requested & ACCESS3_LOOKUP) != 0 && is_dir && can_execute(auth, attr) { - result |= ACCESS3_LOOKUP; - } - - // ACCESS3_MODIFY - alter existing data (write permission) - if (requested & ACCESS3_MODIFY) != 0 && can_write(auth, attr) { - result |= ACCESS3_MODIFY; - } - - // ACCESS3_EXTEND - add new data (write permission) - if (requested & ACCESS3_EXTEND) != 0 && can_write(auth, attr) { - result |= ACCESS3_EXTEND; - } - - // ACCESS3_DELETE - for non-directory files, always 0 (per RFC 1813) - // For directories, this would need to check parent directory permissions - // which is handled at the operation level, not here - if (requested & ACCESS3_DELETE) != 0 { - // DELETE permission is checked at operation time against the parent directory - // For the ACCESS procedure, we return 0 for files (per UNIX semantics) - // and the directory's write permission for directories - if is_dir && can_write(auth, attr) { - result |= ACCESS3_DELETE; - } - } - - // ACCESS3_EXECUTE - execute files (not directories) - if (requested & ACCESS3_EXECUTE) != 0 && !is_dir && can_execute(auth, attr) { - result |= ACCESS3_EXECUTE; - } - - result -} - -/// Check if caller has permission to modify a directory (create, remove, rename entries). -/// This requires write AND execute permission on the directory. -pub fn can_modify_directory(auth: &auth_unix, dir_attr: &fattr3) -> bool { - can_write(auth, dir_attr) && can_execute(auth, dir_attr) -} - -/// Check if caller can delete/rename an entry in a directory. -/// When the directory has the sticky bit set, only root, the directory -/// owner, or the file owner can delete/rename entries. -pub fn can_delete_entry(auth: &auth_unix, dir_attr: &fattr3, entry_attr: &fattr3) -> bool { - if !can_modify_directory(auth, dir_attr) { - return false; - } - if (dir_attr.mode & S_ISVTX) == 0 { - return true; - } - auth.uid == 0 || auth.uid == dir_attr.uid || auth.uid == entry_attr.uid -} - -/// Check if caller is the owner of the file (or root). -/// Used for operations like chmod that require ownership. -pub fn is_owner(auth: &auth_unix, attr: &fattr3) -> bool { - auth.uid == 0 || auth.uid == attr.uid -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::nfsserve::nfs::{ftype3, nfstime3, specdata3}; - - fn make_auth(uid: u32, gid: u32, gids: Vec) -> auth_unix { - auth_unix { - stamp: 0, - machinename: Vec::new(), - uid, - gid, - gids, - } - } - - fn make_attr(mode: u32, uid: u32, gid: u32, ftype: ftype3) -> fattr3 { - fattr3 { - ftype, - mode, - nlink: 1, - uid, - gid, - size: 0, - used: 0, - rdev: specdata3::default(), - fsid: 0, - fileid: 1, - atime: nfstime3::default(), - mtime: nfstime3::default(), - ctime: nfstime3::default(), - } - } - - #[test] - fn test_root_always_allowed() { - let auth = make_auth(0, 0, vec![]); - let attr = make_attr(0o000, 1000, 1000, ftype3::NF3REG); - assert!(can_read(&auth, &attr)); - assert!(can_write(&auth, &attr)); - assert!(can_execute(&auth, &attr)); - } - - #[test] - fn test_owner_permissions() { - let auth = make_auth(1000, 1000, vec![]); - - // Owner read only - let attr = make_attr(0o400, 1000, 2000, ftype3::NF3REG); - assert!(can_read(&auth, &attr)); - assert!(!can_write(&auth, &attr)); - assert!(!can_execute(&auth, &attr)); - - // Owner write only - let attr = make_attr(0o200, 1000, 2000, ftype3::NF3REG); - assert!(!can_read(&auth, &attr)); - assert!(can_write(&auth, &attr)); - assert!(!can_execute(&auth, &attr)); - - // Owner execute only - let attr = make_attr(0o100, 1000, 2000, ftype3::NF3REG); - assert!(!can_read(&auth, &attr)); - assert!(!can_write(&auth, &attr)); - assert!(can_execute(&auth, &attr)); - } - - #[test] - fn test_group_permissions() { - let auth = make_auth(1000, 2000, vec![]); - - // Group read only - let attr = make_attr(0o040, 3000, 2000, ftype3::NF3REG); - assert!(can_read(&auth, &attr)); - assert!(!can_write(&auth, &attr)); - - // Group write only - let attr = make_attr(0o020, 3000, 2000, ftype3::NF3REG); - assert!(!can_read(&auth, &attr)); - assert!(can_write(&auth, &attr)); - } - - #[test] - fn test_auxiliary_group() { - let auth = make_auth(1000, 1000, vec![2000, 3000]); - - // User not owner, but in aux group - let attr = make_attr(0o040, 9999, 2000, ftype3::NF3REG); - assert!(can_read(&auth, &attr)); - - let attr = make_attr(0o040, 9999, 3000, ftype3::NF3REG); - assert!(can_read(&auth, &attr)); - } - - #[test] - fn test_other_permissions() { - let auth = make_auth(1000, 1000, vec![]); - - // Other read only - let attr = make_attr(0o004, 2000, 2000, ftype3::NF3REG); - assert!(can_read(&auth, &attr)); - assert!(!can_write(&auth, &attr)); - - // Other write only - let attr = make_attr(0o002, 2000, 2000, ftype3::NF3REG); - assert!(!can_read(&auth, &attr)); - assert!(can_write(&auth, &attr)); - } - - #[test] - fn test_access_computation() { - let auth = make_auth(1000, 1000, vec![]); - - // Regular file with rwx for owner - let attr = make_attr(0o700, 1000, 1000, ftype3::NF3REG); - let access = compute_access(&auth, &attr, 0x3f); - assert!((access & ACCESS3_READ) != 0); - assert!((access & ACCESS3_MODIFY) != 0); - assert!((access & ACCESS3_EXTEND) != 0); - assert!((access & ACCESS3_EXECUTE) != 0); - // LOOKUP only for directories - assert!((access & ACCESS3_LOOKUP) == 0); - - // Directory with rwx for owner - let attr = make_attr(0o700, 1000, 1000, ftype3::NF3DIR); - let access = compute_access(&auth, &attr, 0x3f); - assert!((access & ACCESS3_READ) != 0); - assert!((access & ACCESS3_LOOKUP) != 0); - assert!((access & ACCESS3_MODIFY) != 0); - assert!((access & ACCESS3_EXTEND) != 0); - assert!((access & ACCESS3_DELETE) != 0); - // EXECUTE only for files - assert!((access & ACCESS3_EXECUTE) == 0); - } -} diff --git a/cli/src/nfsserve/rpcwire.rs b/cli/src/nfsserve/rpcwire.rs deleted file mode 100644 index cda05e93..00000000 --- a/cli/src/nfsserve/rpcwire.rs +++ /dev/null @@ -1,218 +0,0 @@ -use anyhow::anyhow; -use std::io::Cursor; -use std::io::{Read, Write}; -use tracing::{debug, error, trace, warn}; - -use super::context::RPCContext; -use super::rpc::*; -use super::xdr::*; - -use super::mount; -use super::mount_handlers; - -use super::nfs; -use super::nfs_handlers; - -use super::portmap; -use super::portmap_handlers; -use tokio::io::AsyncReadExt; -use tokio::io::AsyncWriteExt; -use tokio::io::DuplexStream; -use tokio::sync::mpsc; - -// Information from RFC 5531 -// https://datatracker.ietf.org/doc/html/rfc5531 - -const NFS_ACL_PROGRAM: u32 = 100227; -const NFS_ID_MAP_PROGRAM: u32 = 100270; -const NFS_METADATA_PROGRAM: u32 = 200024; - -async fn handle_rpc( - input: &mut impl Read, - output: &mut impl Write, - mut context: RPCContext, -) -> Result { - let mut recv = rpc_msg::default(); - recv.deserialize(input)?; - let xid = recv.xid; - if let rpc_body::CALL(call) = recv.body { - if let auth_flavor::AUTH_UNIX = call.cred.flavor { - let mut auth = auth_unix::default(); - auth.deserialize(&mut Cursor::new(&call.cred.body))?; - context.auth = auth; - } - if call.rpcvers != 2 { - warn!("Invalid RPC version {} != 2", call.rpcvers); - rpc_vers_mismatch(xid).serialize(output)?; - return Ok(true); - } - - if context - .transaction_tracker - .is_retransmission(xid, &context.client_addr) - { - // This is a retransmission - // Drop the message and return - debug!( - "Retransmission detected, xid: {}, client_addr: {}, call: {:?}", - xid, context.client_addr, call - ); - return Ok(false); - } - - let res = { - if call.prog == nfs::PROGRAM { - nfs_handlers::handle_nfs(xid, call, input, output, &context).await - } else if call.prog == portmap::PROGRAM { - portmap_handlers::handle_portmap(xid, call, input, output, &context) - } else if call.prog == mount::PROGRAM { - mount_handlers::handle_mount(xid, call, input, output, &context).await - } else if call.prog == NFS_ACL_PROGRAM - || call.prog == NFS_ID_MAP_PROGRAM - || call.prog == NFS_METADATA_PROGRAM - { - trace!("ignoring NFS_ACL packet"); - prog_unavail_reply_message(xid).serialize(output)?; - Ok(()) - } else { - warn!( - "Unknown RPC Program number {} != {}", - call.prog, - nfs::PROGRAM - ); - prog_unavail_reply_message(xid).serialize(output)?; - Ok(()) - } - } - .map(|_| true); - context - .transaction_tracker - .mark_processed(xid, &context.client_addr); - res - } else { - error!("Unexpectedly received a Reply instead of a Call"); - Err(anyhow!("Bad RPC Call format")) - } -} - -/// RFC 1057 Section 10 -/// When RPC messages are passed on top of a byte stream transport -/// protocol (like TCP), it is necessary to delimit one message from -/// another in order to detect and possibly recover from protocol errors. -/// This is called record marking (RM). Sun uses this RM/TCP/IP -/// transport for passing RPC messages on TCP streams. One RPC message -/// fits into one RM record. -/// -/// A record is composed of one or more record fragments. A record -/// fragment is a four-byte header followed by 0 to (2**31) - 1 bytes of -/// fragment data. The bytes encode an unsigned binary number; as with -/// XDR integers, the byte order is from highest to lowest. The number -/// encodes two values -- a boolean which indicates whether the fragment -/// is the last fragment of the record (bit value 1 implies the fragment -/// is the last fragment) and a 31-bit unsigned binary value which is the -/// length in bytes of the fragment's data. The boolean value is the -/// highest-order bit of the header; the length is the 31 low-order bits. -/// (Note that this record specification is NOT in XDR standard form!) -async fn read_fragment( - socket: &mut DuplexStream, - append_to: &mut Vec, -) -> Result { - let mut header_buf = [0_u8; 4]; - socket.read_exact(&mut header_buf).await?; - let fragment_header = u32::from_be_bytes(header_buf); - let is_last = (fragment_header & (1 << 31)) > 0; - let length = (fragment_header & ((1 << 31) - 1)) as usize; - trace!("Reading fragment length:{}, last:{}", length, is_last); - let start_offset = append_to.len(); - append_to.resize(append_to.len() + length, 0); - socket.read_exact(&mut append_to[start_offset..]).await?; - trace!( - "Finishing Reading fragment length:{}, last:{}", - length, - is_last - ); - Ok(is_last) -} - -pub async fn write_fragment( - socket: &mut tokio::net::TcpStream, - buf: &[u8], -) -> Result<(), anyhow::Error> { - // TODO: split into many fragments - assert!(buf.len() < (1 << 31)); - // set the last flag - let fragment_header = buf.len() as u32 + (1 << 31); - let header_buf = u32::to_be_bytes(fragment_header); - socket.write_all(&header_buf).await?; - trace!("Writing fragment length:{}", buf.len()); - socket.write_all(buf).await?; - Ok(()) -} - -pub type SocketMessageType = Result, anyhow::Error>; - -/// The Socket Message Handler reads from a TcpStream and spawns off -/// subtasks to handle each message. replies are queued into the -/// reply_send_channel. -#[derive(Debug)] -pub struct SocketMessageHandler { - cur_fragment: Vec, - socket_receive_channel: DuplexStream, - reply_send_channel: mpsc::UnboundedSender, - context: RPCContext, -} - -impl SocketMessageHandler { - /// Creates a new SocketMessageHandler with the receiver for queued message replies - pub fn new( - context: &RPCContext, - ) -> ( - Self, - DuplexStream, - mpsc::UnboundedReceiver, - ) { - let (socksend, sockrecv) = tokio::io::duplex(256000); - let (msgsend, msgrecv) = mpsc::unbounded_channel(); - ( - Self { - cur_fragment: Vec::new(), - socket_receive_channel: sockrecv, - reply_send_channel: msgsend, - context: context.clone(), - }, - socksend, - msgrecv, - ) - } - - /// Reads a fragment from the socket. This should be looped. - pub async fn read(&mut self) -> Result<(), anyhow::Error> { - let is_last = - read_fragment(&mut self.socket_receive_channel, &mut self.cur_fragment).await?; - if is_last { - let fragment = std::mem::take(&mut self.cur_fragment); - let context = self.context.clone(); - let send = self.reply_send_channel.clone(); - tokio::spawn(async move { - let mut write_buf: Vec = Vec::new(); - let mut write_cursor = Cursor::new(&mut write_buf); - let maybe_reply = - handle_rpc(&mut Cursor::new(fragment), &mut write_cursor, context).await; - match maybe_reply { - Err(e) => { - error!("RPC Error: {:?}", e); - let _ = send.send(Err(e)); - } - Ok(true) => { - let _ = std::io::Write::flush(&mut write_cursor); - let _ = send.send(Ok(write_buf)); - } - Ok(false) => { - // do not reply - } - } - }); - } - Ok(()) - } -} diff --git a/cli/src/nfsserve/tcp.rs b/cli/src/nfsserve/tcp.rs deleted file mode 100644 index fe27810b..00000000 --- a/cli/src/nfsserve/tcp.rs +++ /dev/null @@ -1,232 +0,0 @@ -use super::context::RPCContext; -use super::rpcwire::*; -use super::transaction_tracker::TransactionTracker; -use super::vfs::NFSFileSystem; -use anyhow; -use async_trait::async_trait; -use std::net::SocketAddr; -use std::sync::Arc; -use std::time::Duration; -use std::{io, net::IpAddr}; -use tokio::io::AsyncWriteExt; -use tokio::net::TcpListener; -use tokio::sync::mpsc; -use tracing::{debug, error}; - -/// A NFS Tcp Connection Handler -pub struct NFSTcpListener { - listener: TcpListener, - port: u16, - arcfs: Arc, - mount_signal: Option>, - export_name: Arc, - transaction_tracker: Arc, -} - -pub fn generate_host_ip(hostnum: u16) -> String { - format!( - "127.88.{}.{}", - ((hostnum >> 8) & 0xFF) as u8, - (hostnum & 0xFF) as u8 - ) -} - -/// processes an established socket -async fn process_socket( - mut socket: tokio::net::TcpStream, - context: RPCContext, -) -> Result<(), anyhow::Error> { - let (mut message_handler, mut socksend, mut msgrecvchan) = SocketMessageHandler::new(&context); - let _ = socket.set_nodelay(true); - - tokio::spawn(async move { - loop { - if let Err(e) = message_handler.read().await { - debug!("Message loop broken due to {:?}", e); - break; - } - } - }); - loop { - tokio::select! { - _ = socket.readable() => { - let mut buf = [0; 128000]; - - match socket.try_read(&mut buf) { - Ok(0) => { - return Ok(()); - } - Ok(n) => { - let _ = socksend.write_all(&buf[..n]).await; - } - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - continue; - } - Err(e) => { - debug!("Message handling closed : {:?}", e); - return Err(e.into()); - } - } - - }, - reply = msgrecvchan.recv() => { - match reply { - Some(Err(e)) => { - debug!("Message handling closed : {:?}", e); - return Err(e); - } - Some(Ok(msg)) => { - if let Err(e) = write_fragment(&mut socket, &msg).await { - error!("Write error {:?}", e); - } - } - None => { - return Err(anyhow::anyhow!("Unexpected socket context termination")); - } - } - } - } - } -} - -#[async_trait] -pub trait NFSTcp: Send + Sync { - /// Gets the true listening port. Useful if the bound port number is 0 - fn get_listen_port(&self) -> u16; - - /// Gets the true listening IP. Useful on windows when the IP may be random - fn get_listen_ip(&self) -> IpAddr; - - /// Sets a mount listener. A "true" signal will be sent on a mount - /// and a "false" will be sent on an unmount - fn set_mount_listener(&mut self, signal: mpsc::Sender); - - /// Loops forever and never returns handling all incoming connections. - async fn handle_forever(&self) -> io::Result<()>; -} - -impl NFSTcpListener { - /// Binds to a ipstr of the form [ip address]:port. For instance - /// "127.0.0.1:12000". fs is an instance of an implementation - /// of NFSFileSystem. - pub async fn bind(ipstr: &str, fs: T) -> io::Result> { - let (ip, port) = ipstr.split_once(':').ok_or_else(|| { - io::Error::new( - io::ErrorKind::AddrNotAvailable, - "IP Address must be of form ip:port", - ) - })?; - let port = port.parse::().map_err(|_| { - io::Error::new( - io::ErrorKind::AddrNotAvailable, - "Port not in range 0..=65535", - ) - })?; - - let arcfs: Arc = Arc::new(fs); - - if ip == "auto" { - let mut num_tries_left = 32; - - for try_ip in 1u16.. { - let ip = generate_host_ip(try_ip); - - let result = NFSTcpListener::bind_internal(&ip, port, arcfs.clone()).await; - - match &result { - Err(_) => { - if num_tries_left == 0 { - return result; - } else { - num_tries_left -= 1; - continue; - } - } - Ok(_) => { - return result; - } - } - } - unreachable!(); // Does not detect automatically that loop above never terminates. - } else { - // Otherwise, try this. - NFSTcpListener::bind_internal(ip, port, arcfs).await - } - } - - async fn bind_internal(ip: &str, port: u16, arcfs: Arc) -> io::Result> { - let ipstr = format!("{ip}:{port}"); - let listener = TcpListener::bind(&ipstr).await?; - debug!("Listening on {:?}", &ipstr); - - let port = match listener.local_addr().unwrap() { - SocketAddr::V4(s) => s.port(), - SocketAddr::V6(s) => s.port(), - }; - Ok(NFSTcpListener { - listener, - port, - arcfs, - mount_signal: None, - export_name: Arc::from("/".to_string()), - transaction_tracker: Arc::new(TransactionTracker::new(Duration::from_secs(60))), - }) - } - - /// Sets an optional NFS export name. - /// - /// - `export_name`: The desired export name without slashes. - /// - /// Example: Name `foo` results in the export path `/foo`. - /// Default path is `/` if not set. - pub fn with_export_name>(&mut self, export_name: S) { - self.export_name = Arc::new(format!( - "/{}", - export_name - .as_ref() - .trim_end_matches('/') - .trim_start_matches('/') - )) - } -} - -#[async_trait] -impl NFSTcp for NFSTcpListener { - /// Gets the true listening port. Useful if the bound port number is 0 - fn get_listen_port(&self) -> u16 { - let addr = self.listener.local_addr().unwrap(); - addr.port() - } - - fn get_listen_ip(&self) -> IpAddr { - let addr = self.listener.local_addr().unwrap(); - addr.ip() - } - - /// Sets a mount listener. A "true" signal will be sent on a mount - /// and a "false" will be sent on an unmount - fn set_mount_listener(&mut self, signal: mpsc::Sender) { - self.mount_signal = Some(signal); - } - - /// Loops forever and never returns handling all incoming connections. - async fn handle_forever(&self) -> io::Result<()> { - loop { - let (socket, _) = self.listener.accept().await?; - let context = RPCContext { - local_port: self.port, - client_addr: socket.peer_addr().unwrap().to_string(), - auth: super::rpc::auth_unix::default(), - vfs: self.arcfs.clone(), - mount_signal: self.mount_signal.clone(), - export_name: self.export_name.clone(), - transaction_tracker: self.transaction_tracker.clone(), - }; - debug!("Accepting connection from {}", context.client_addr); - debug!("Accepting socket {:?} {:?}", socket, context); - tokio::spawn(async move { - let _ = process_socket(socket, context).await; - }); - } - } -} diff --git a/cli/src/nfsserve/transaction_tracker.rs b/cli/src/nfsserve/transaction_tracker.rs deleted file mode 100644 index 18326150..00000000 --- a/cli/src/nfsserve/transaction_tracker.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::collections::HashMap; -use std::sync::Mutex; -use std::time::{Duration, SystemTime}; - -/// `TransactionTracker` tracks the state of transactions to detect retransmissions. -pub struct TransactionTracker { - retention_period: Duration, - transactions: Mutex>, -} - -impl TransactionTracker { - pub fn new(retention_period: Duration) -> Self { - Self { - retention_period, - transactions: Mutex::new(HashMap::new()), - } - } - - /// Checks if the transaction is a retransmission. - /// If it's a new transaction, it is marked as `InProgress`. - /// - /// Returns `true` if the transaction is a retransmission, `false` otherwise. - pub fn is_retransmission(&self, xid: u32, client_addr: &str) -> bool { - let key = (xid, client_addr.to_string()); - let mut transactions = self - .transactions - .lock() - .expect("unable to unlock transactions mutex"); - housekeeping(&mut transactions, self.retention_period); - if let std::collections::hash_map::Entry::Vacant(e) = transactions.entry(key) { - e.insert(TransactionState::InProgress); - false - } else { - true - } - } - - /// Marks the transaction as processed. - pub fn mark_processed(&self, xid: u32, client_addr: &str) { - let key = (xid, client_addr.to_string()); - let completion_time = SystemTime::now(); - let mut transactions = self - .transactions - .lock() - .expect("unable to unlock transactions mutex"); - if let Some(tx) = transactions.get_mut(&key) { - *tx = TransactionState::Completed(completion_time); - } - } -} - -fn housekeeping(transactions: &mut HashMap<(u32, String), TransactionState>, max_age: Duration) { - let mut cutoff = SystemTime::now() - max_age; - transactions.retain(|_, v| match v { - TransactionState::InProgress => true, - TransactionState::Completed(completion_time) => completion_time >= &mut cutoff, - }); -} - -pub enum TransactionState { - InProgress, - Completed(SystemTime), -} diff --git a/cli/src/sandbox/darwin.rs b/cli/src/sandbox/darwin.rs deleted file mode 100644 index 2a21a1f6..00000000 --- a/cli/src/sandbox/darwin.rs +++ /dev/null @@ -1,287 +0,0 @@ -//! macOS Sandbox sandbox support for AgentFS. -//! -//! This module provides kernel-enforced sandboxing using macOS's sandbox-exec -//! with dynamically generated Sandbox profiles. When enabled, the spawned -//! process can only access the NFS mountpoint and explicitly allowed paths. -//! -//! # Example -//! -//! ```ignore -//! let config = SandboxConfig { -//! mountpoint: PathBuf::from("/Users/me/.agentfs/run/abc/mnt"), -//! allow_paths: vec![PathBuf::from("/tmp")], -//! allow_network: false, -//! session_id: "abc123".to_string(), -//! }; -//! let profile = generate_sandbox_profile(&config); -//! let wrapped = wrap_command_with_sandbox(&config, "zsh", &[]); -//! ``` - -use std::path::{Path, PathBuf}; -use std::process::Command; - -/// Configuration for the Sandbox sandbox. -#[derive(Debug, Clone)] -pub struct SandboxConfig { - /// The NFS mountpoint (primary read/write location). - pub mountpoint: PathBuf, - - /// Additional paths to allow read/write access. - pub allow_paths: Vec, - - /// Additional paths to allow read-only access. - pub allow_read_paths: Vec, - - /// Whether to allow network access. - pub allow_network: bool, - - /// Session ID for log filtering (used in violation messages). - pub session_id: String, -} - -impl Default for SandboxConfig { - fn default() -> Self { - Self { - mountpoint: PathBuf::new(), - allow_paths: Vec::new(), - allow_read_paths: Vec::new(), - allow_network: false, - session_id: String::new(), - } - } -} - -/// Generate a Sandbox profile for AgentFS. -/// -/// The profile allows most operations but restricts file writes to: -/// - The NFS mountpoint (which overlays CWD) -/// - Temp directories (/tmp, /var/folders) -/// - Explicitly allowed paths (e.g., ~/.claude, ~/.config, etc.) -/// -/// This approach is simpler and more reliable than trying to enumerate -/// all paths a process might need to read. -pub fn generate_sandbox_profile(config: &SandboxConfig) -> String { - let mut profile = Vec::new(); - let log_tag = format!("agentfs-{}", config.session_id); - - // Version and deny by default for file writes - profile.push("(version 1)".to_string()); - profile.push(format!( - r#"(deny default (with message "agentfs-{}: write denied"))"#, - config.session_id - )); - profile.push(format!("; Log tag: {}", log_tag)); - - // ========================================================================= - // Allow most operations - we only want to restrict file writes - // ========================================================================= - profile.push("; Allow most operations".to_string()); - profile.push("(allow process*)".to_string()); - profile.push("(allow signal)".to_string()); - profile.push("(allow mach*)".to_string()); - profile.push("(allow sysctl*)".to_string()); - profile.push("(allow system*)".to_string()); - profile.push("(allow ipc*)".to_string()); - profile.push("(allow pseudo-tty)".to_string()); - - // ========================================================================= - // Allow all file reads - the overlay handles copy-on-write - // ========================================================================= - profile.push("; Allow all file reads".to_string()); - profile.push("(allow file-read*)".to_string()); - - // ========================================================================= - // Writable paths - these are the only places writes are allowed - // ========================================================================= - profile.push("; Writable paths".to_string()); - - // The NFS mountpoint - primary workspace (overlays CWD) - let mountpoint_str = config.mountpoint.to_string_lossy(); - profile.push(format!( - r#"(allow file-write* (subpath "{}"))"#, - mountpoint_str - )); - - // The run directory (for zsh config, etc.) - if let Some(parent) = config.mountpoint.parent() { - let run_dir_str = parent.to_string_lossy(); - profile.push(format!( - r#"(allow file-write* (subpath "{}"))"#, - run_dir_str - )); - } - - // Temp directories (many tools require these) - profile.push(r#"(allow file-write* (subpath "/private/tmp"))"#.to_string()); - profile.push(r#"(allow file-write* (subpath "/tmp"))"#.to_string()); - profile.push(r#"(allow file-write* (subpath "/var/tmp"))"#.to_string()); - - // Private var folders (per-user temp space) - profile.push(r#"(allow file-write* (subpath "/private/var/folders"))"#.to_string()); - - // Device files (terminals, etc.) - profile.push(r#"(allow file-write* (subpath "/dev"))"#.to_string()); - profile.push(r#"(allow file-ioctl (subpath "/dev"))"#.to_string()); - - // Additional writable paths from config - for path in &config.allow_paths { - let path_str = path.to_string_lossy(); - profile.push(format!(r#"(allow file-write* (subpath "{}"))"#, path_str)); - } - - // ========================================================================= - // Network access - // ========================================================================= - profile.push("; Network".to_string()); - if config.allow_network { - profile.push("(allow network*)".to_string()); - } else { - // Only allow localhost for NFS - profile.push(r#"(allow network* (remote ip "localhost:*"))"#.to_string()); - profile.push(r#"(allow network* (local ip "localhost:*"))"#.to_string()); - } - - // ========================================================================= - // Security and Keychain - needed for credential storage - // ========================================================================= - profile.push("; Security and Keychain".to_string()); - profile.push(r#"(allow file-write* (subpath "/private/var/db/mds"))"#.to_string()); - profile.push( - r#"(allow file-write* (regex #"^/private/var/folders/[^/]+/[^/]+/C/mds/"))"#.to_string(), - ); - profile - .push(r#"(allow file-write* (regex #"^/private/var/folders/[^/]+/[^/]+/T/"))"#.to_string()); - // User Library paths for Keychain and security services - if let Some(home) = dirs::home_dir() { - let home_str = home.to_string_lossy(); - profile.push(format!( - r#"(allow file-write* (subpath "{}/Library"))"#, - home_str - )); - } - // System Library for preferences - profile.push(r#"(allow file-write* (subpath "/Library/Preferences"))"#.to_string()); - profile.push(r#"(allow file-write* (subpath "/Library/Keychains"))"#.to_string()); - // Authorization and user preference operations - profile.push("(allow authorization-right-obtain)".to_string()); - profile.push("(allow user-preference-write)".to_string()); - profile.push("(allow user-preference-read)".to_string()); - - profile.join("\n") -} - -/// Wrap a command with sandbox-exec. -/// -/// Returns a Command configured to run the given program inside the sandbox. -pub fn wrap_command_with_sandbox( - config: &SandboxConfig, - program: &Path, - args: &[String], -) -> Command { - let profile = generate_sandbox_profile(config); - - let mut cmd = Command::new("sandbox-exec"); - cmd.arg("-p").arg(&profile); - cmd.arg(program); - cmd.args(args); - cmd.current_dir(&config.mountpoint); - - // Set environment variables - cmd.env("AGENTFS", "1"); - cmd.env("AGENTFS_SANDBOX", "macos-sandbox"); - - cmd -} - -/// Generate a minimal Sandbox profile for testing. -/// -/// This profile is more permissive and useful for debugging sandbox issues. -pub fn generate_permissive_profile(config: &SandboxConfig) -> String { - let mut profile = Vec::new(); - let log_tag = format!("agentfs-{}", config.session_id); - - profile.push("(version 1)".to_string()); - - // Log denials but don't block most things - profile.push(format!("(deny default (with message \"{}\")))", log_tag)); - - // Allow almost everything for debugging - profile.push("(allow process*)".to_string()); - profile.push("(allow file-read*)".to_string()); - profile.push("(allow mach*)".to_string()); - profile.push("(allow sysctl*)".to_string()); - profile.push("(allow signal)".to_string()); - profile.push("(allow ipc*)".to_string()); - profile.push("(allow pseudo-tty)".to_string()); - profile.push("(allow system*)".to_string()); - - // Only restrict writes to outside mountpoint - let mountpoint_str = config.mountpoint.to_string_lossy(); - profile.push(format!( - r#"(allow file-write* (subpath "{}"))"#, - mountpoint_str - )); - profile.push(r#"(allow file-write* (subpath "/private/tmp"))"#.to_string()); - profile.push(r#"(allow file-write* (subpath "/tmp"))"#.to_string()); - profile.push(r#"(allow file-write* (subpath "/private/var/folders"))"#.to_string()); - - // Network - if config.allow_network { - profile.push("(allow network*)".to_string()); - } else { - profile.push("(allow network* (remote ip \"localhost:*\"))".to_string()); - profile.push("(allow network* (local ip \"localhost:*\"))".to_string()); - } - - profile.join("\n") -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_generate_profile() { - let config = SandboxConfig { - mountpoint: PathBuf::from("/Users/test/.agentfs/run/abc/mnt"), - allow_paths: vec![], - allow_read_paths: vec![], - allow_network: false, - session_id: "test123".to_string(), - }; - - let profile = generate_sandbox_profile(&config); - - assert!(profile.contains("(version 1)")); - assert!(profile.contains("(deny default")); - assert!(profile.contains("agentfs-test123: write denied")); - assert!(profile.contains("/Users/test/.agentfs/run/abc/mnt")); - } - - #[test] - fn test_profile_with_network() { - let config = SandboxConfig { - mountpoint: PathBuf::from("/mnt"), - allow_network: true, - ..Default::default() - }; - - let profile = generate_sandbox_profile(&config); - assert!(profile.contains("(allow network*)")); - } - - #[test] - fn test_profile_with_custom_paths() { - let config = SandboxConfig { - mountpoint: PathBuf::from("/mnt"), - allow_paths: vec![PathBuf::from("/custom/writable")], - allow_read_paths: vec![PathBuf::from("/custom/readonly")], - ..Default::default() - }; - - let profile = generate_sandbox_profile(&config); - // Writable paths should be included - assert!(profile.contains("/custom/writable")); - // Note: allow_read_paths is not used since we allow all reads by default - } -} diff --git a/cli/src/sandbox/linux_ptrace.rs b/cli/src/sandbox/linux_ptrace.rs deleted file mode 100644 index 5eb32ca6..00000000 --- a/cli/src/sandbox/linux_ptrace.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Ptrace-based sandbox implementation using reverie. -//! -//! This module provides syscall interception via ptrace for filesystem -//! virtualization. This is experimental and requires root or CAP_SYS_PTRACE. - -use agentfs_sandbox::{ - init_fd_tables, init_mount_table, init_strace, MountTable, Sandbox, SqliteVfs, -}; -use reverie_process::Command; -use reverie_ptrace::TracerBuilder; -use std::{path::PathBuf, sync::Arc}; - -/// Run a command using the experimental ptrace-based syscall interception sandbox. -pub async fn run_cmd(strace: bool, command: PathBuf, args: Vec) { - eprintln!("Welcome to AgentFS!"); - eprintln!(); - - let mut mount_table = MountTable::new(); - - // Default mount: agent.db at /agent - let db_path = PathBuf::from("agent.db"); - let mount_point = PathBuf::from("/agent"); - - eprintln!("The following mount points are sandboxed:"); - eprintln!( - " - {} -> {} (agentfs)", - mount_point.display(), - db_path.display() - ); - eprintln!(); - - let vfs = SqliteVfs::new(&db_path, mount_point.clone()) - .await - .expect("Failed to create AgentFS VFS"); - mount_table.add_mount(mount_point, Arc::new(vfs)); - - init_mount_table(mount_table); - init_fd_tables(); - init_strace(strace); - - let mut cmd = Command::new(command); - for arg in args { - cmd.arg(arg); - } - - let tracer = TracerBuilder::::new(cmd).spawn().await.unwrap(); - - let (status, _) = tracer.wait().await.unwrap(); - status.raise_or_exit() -} diff --git a/cli/src/sandbox/mod.rs b/cli/src/sandbox/mod.rs deleted file mode 100644 index a648265f..00000000 --- a/cli/src/sandbox/mod.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! Sandbox implementations for running commands in isolated environments. -//! -//! This module provides platform-specific sandbox approaches: -//! - `linux`: FUSE + namespace-based sandbox with copy-on-write filesystem -//! - `linux_ptrace`: ptrace-based syscall interception sandbox (experimental) -//! - `darwin`: Kernel-enforced sandbox using sandbox-exec - -use std::collections::BTreeMap; -use std::path::PathBuf; - -#[cfg(all(target_os = "linux", feature = "sandbox"))] -pub mod linux; - -#[cfg(all(target_os = "linux", feature = "sandbox"))] -pub mod linux_ptrace; - -#[cfg(all(target_os = "macos", feature = "sandbox"))] -pub mod darwin; - -/// Group paths by parent directory and format using brace expansion. -/// -/// For example, given paths: -/// - /home/user/.claude -/// - /home/user/.claude.json -/// - /home/user/.codex -/// - /home/user/.npm -/// -/// Returns: `["/home/user/{.claude, .claude.json, .codex, .npm}"]` -pub fn group_paths_by_parent(paths: &[PathBuf]) -> Vec { - let mut groups: BTreeMap> = BTreeMap::new(); - - for path in paths { - let (parent, name) = match (path.parent(), path.file_name()) { - (Some(parent), Some(name)) => { - (parent.to_path_buf(), name.to_string_lossy().to_string()) - } - _ => (PathBuf::new(), path.display().to_string()), - }; - groups.entry(parent).or_default().push(name); - } - - groups - .into_iter() - .map(|(parent, mut names)| { - names.sort(); - let parent_str = parent.display().to_string(); - if names.len() == 1 { - if parent_str.is_empty() { - names.remove(0) - } else { - format!("{}/{}", parent_str, names[0]) - } - } else { - format!("{}/{{{}}}", parent_str, names.join(", ")) - } - }) - .collect() -} diff --git a/cli/tests/all.sh b/cli/tests/all.sh deleted file mode 100755 index 3acff734..00000000 --- a/cli/tests/all.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -set -e - -DIR="$(dirname "$0")" - -"$DIR/test-init.sh" - -# Syscall tests in three configurations: -# 1. Linux baseline - establishes expected behavior -"$DIR/test-linux-syscalls.sh" - -# 2. ptrace-based sandbox (--experimental-sandbox) -# TODO: The test cases don't currently pass with ptrace-based virtualization -# because of compatibility issues. -# "$DIR/test-run-experimental-syscalls.sh" - -# 3. FUSE overlay (agentfs run) - tests copy-on-write -"$DIR/test-run-syscalls.sh" || true # Requires user namespaces (may fail in CI) - -"$DIR/test-run-bash.sh" || true # Requires user namespaces (may fail in CI) -"$DIR/test-run-git.sh" || true # Requires user namespaces (may fail in CI) -# Short corruption/concurrency smoke; the test prints SKIP and exits 0 if -# Linux user namespace/FUSE prerequisites are unavailable. -CORRUPTION_TORTURE_WORKERS="${CORRUPTION_TORTURE_WORKERS:-2}" \ -CORRUPTION_TORTURE_ITERATIONS="${CORRUPTION_TORTURE_ITERATIONS:-2}" \ -CORRUPTION_TORTURE_TIMEOUT="${CORRUPTION_TORTURE_TIMEOUT:-60}" \ -CORRUPTION_TORTURE_INTEGRITY_INTERVAL="${CORRUPTION_TORTURE_INTEGRITY_INTERVAL:-1}" \ -"$DIR/test-corruption-torture.sh" -"$DIR/test-mount.sh" -"$DIR/test-overlay-whiteout.sh" -"$DIR/test-overlay-delta-in-base-dir.sh" -"$DIR/test-fuse-cache-invalidation.sh" -"$DIR/test-symlinks.sh" || true # Requires user namespaces (may fail in CI) diff --git a/cli/tests/test-fuse-cache-invalidation.sh b/cli/tests/test-fuse-cache-invalidation.sh deleted file mode 100755 index 6fff505d..00000000 --- a/cli/tests/test-fuse-cache-invalidation.sh +++ /dev/null @@ -1,255 +0,0 @@ -#!/bin/sh -# -# Test that FUSE kernel cache is properly invalidated after mutations. -# -# After readdirplus populates the dcache, unlink/rmdir/rename must -# invalidate the affected entries so subsequent readdir sees the change. -# -set -e - -echo -n "TEST fuse cache invalidation after mutations... " - -TEST_AGENT_ID="test-fuse-cache-inval-agent" -MOUNTPOINT="/tmp/agentfs-test-cache-inval-$$" - -cleanup() { - # Unmount if mounted - fusermount -u "$MOUNTPOINT" 2>/dev/null || true - # Wait for FUSE process to exit after unmount - [ -n "$MOUNT_PID" ] && wait $MOUNT_PID 2>/dev/null || true - # Remove mountpoint - rmdir "$MOUNTPOINT" 2>/dev/null || true - # Remove test database - rm -f ".agentfs/${TEST_AGENT_ID}.db" ".agentfs/${TEST_AGENT_ID}.db-shm" ".agentfs/${TEST_AGENT_ID}.db-wal" -} - -# Ensure cleanup on exit -trap cleanup EXIT - -# Clean up any existing test artifacts -cleanup - -# Initialize the database -cargo run -- init "$TEST_AGENT_ID" > /dev/null 2>&1 - -# Create mountpoint -mkdir -p "$MOUNTPOINT" - -# Mount in foreground mode (background it ourselves so we can control it) -cargo run -- mount ".agentfs/${TEST_AGENT_ID}.db" "$MOUNTPOINT" --foreground & -MOUNT_PID=$! - -# Wait for mount to be ready -MAX_WAIT=10 -WAITED=0 -while [ $WAITED -lt $MAX_WAIT ]; do - if mountpoint -q "$MOUNTPOINT" 2>/dev/null; then - break - fi - sleep 0.5 - WAITED=$((WAITED + 1)) -done - -if ! mountpoint -q "$MOUNTPOINT" 2>/dev/null; then - echo "FAILED: mount did not become ready in time" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test 1: unlink should not leave stale entries in readdir -# Populate the directory with files -echo "content1" > "$MOUNTPOINT/file1.txt" -echo "content2" > "$MOUNTPOINT/file2.txt" -echo "content3" > "$MOUNTPOINT/file3.txt" - -# Prime the kernel dcache via stat + ls (readdirplus populates entries) -ls -la "$MOUNTPOINT" > /dev/null -stat "$MOUNTPOINT/file1.txt" > /dev/null 2>&1 -stat "$MOUNTPOINT/file2.txt" > /dev/null 2>&1 -stat "$MOUNTPOINT/file3.txt" > /dev/null 2>&1 -ls -la "$MOUNTPOINT" > /dev/null - -# Delete file1 and verify readdir no longer shows it -rm "$MOUNTPOINT/file1.txt" - -LS_OUTPUT=$(ls "$MOUNTPOINT") -if echo "$LS_OUTPUT" | grep -q "file1.txt"; then - echo "FAILED: readdir still shows file1.txt after unlink" - echo "ls output was: $LS_OUTPUT" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -if ! echo "$LS_OUTPUT" | grep -q "file2.txt"; then - echo "FAILED: file2.txt disappeared" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi -if stat "$MOUNTPOINT/file1.txt" > /dev/null 2>&1; then - echo "FAILED: stat still resolves file1.txt after unlink" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test 2: rmdir should not leave stale entries in readdir -mkdir "$MOUNTPOINT/subdir" -ls -la "$MOUNTPOINT" > /dev/null -stat "$MOUNTPOINT/subdir" > /dev/null 2>&1 -ls -la "$MOUNTPOINT" > /dev/null - -rmdir "$MOUNTPOINT/subdir" - -LS_OUTPUT=$(ls "$MOUNTPOINT") -if echo "$LS_OUTPUT" | grep -q "subdir"; then - echo "FAILED: readdir still shows subdir after rmdir" - echo "ls output was: $LS_OUTPUT" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi -if stat "$MOUNTPOINT/subdir" > /dev/null 2>&1; then - echo "FAILED: stat still resolves subdir after rmdir" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test 3: rename should not leave stale source entry in readdir -echo "rename me" > "$MOUNTPOINT/before.txt" -ls -la "$MOUNTPOINT" > /dev/null -stat "$MOUNTPOINT/before.txt" > /dev/null 2>&1 -ls -la "$MOUNTPOINT" > /dev/null - -mv "$MOUNTPOINT/before.txt" "$MOUNTPOINT/after.txt" - -LS_OUTPUT=$(ls "$MOUNTPOINT") -if echo "$LS_OUTPUT" | grep -q "before.txt"; then - echo "FAILED: readdir still shows before.txt after rename" - echo "ls output was: $LS_OUTPUT" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -if ! echo "$LS_OUTPUT" | grep -q "after.txt"; then - echo "FAILED: after.txt not visible after rename" - echo "ls output was: $LS_OUTPUT" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi -if stat "$MOUNTPOINT/before.txt" > /dev/null 2>&1; then - echo "FAILED: stat still resolves before.txt after rename" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi -if [ "$(cat "$MOUNTPOINT/after.txt")" != "rename me" ]; then - echo "FAILED: after.txt content is stale after rename" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test 4: create must defeat a cached negative dentry -# Prime a negative dentry: stat a name that doesn't exist yet -ls -la "$MOUNTPOINT" > /dev/null -stat "$MOUNTPOINT/negfile.txt" > /dev/null 2>&1 || true # caches ENOENT -ls -la "$MOUNTPOINT" > /dev/null # readdirplus confirms absence - -echo "new file" > "$MOUNTPOINT/negfile.txt" - -# stat must resolve (not serve cached ENOENT) -if ! stat "$MOUNTPOINT/negfile.txt" > /dev/null 2>&1; then - echo "FAILED: stat returns ENOENT for negfile.txt after create (negative dentry not invalidated)" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# readdir must list it -LS_OUTPUT=$(ls "$MOUNTPOINT") -if ! echo "$LS_OUTPUT" | grep -q "negfile.txt"; then - echo "FAILED: readdir does not show negfile.txt after create" - echo "ls output was: $LS_OUTPUT" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test 5: mkdir must defeat a cached negative dentry -ls -la "$MOUNTPOINT" > /dev/null -stat "$MOUNTPOINT/negdir" > /dev/null 2>&1 || true # caches ENOENT -ls -la "$MOUNTPOINT" > /dev/null # readdirplus confirms absence - -mkdir "$MOUNTPOINT/negdir" - -if ! stat "$MOUNTPOINT/negdir" > /dev/null 2>&1; then - echo "FAILED: stat returns ENOENT for negdir after mkdir (negative dentry not invalidated)" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -LS_OUTPUT=$(ls "$MOUNTPOINT") -if ! echo "$LS_OUTPUT" | grep -q "negdir"; then - echo "FAILED: readdir does not show negdir after mkdir" - echo "ls output was: $LS_OUTPUT" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test 6: truncate must invalidate stale attrs and cached file data -printf "abcdefghij" > "$MOUNTPOINT/truncate.txt" -cat "$MOUNTPOINT/truncate.txt" > /dev/null -stat "$MOUNTPOINT/truncate.txt" > /dev/null 2>&1 -truncate -s 4 "$MOUNTPOINT/truncate.txt" - -TRUNC_SIZE=$(wc -c < "$MOUNTPOINT/truncate.txt" | tr -d ' ') -if [ "$TRUNC_SIZE" != "4" ]; then - echo "FAILED: truncate.txt size is stale after truncate: $TRUNC_SIZE" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi -TRUNC_CONTENT=$(cat "$MOUNTPOINT/truncate.txt") -if [ "$TRUNC_CONTENT" != "abcd" ]; then - echo "FAILED: truncate.txt content is stale after truncate: $TRUNC_CONTENT" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test 7: repeated read/open cache must not serve stale data after write -printf "cache-before" > "$MOUNTPOINT/keep-cache.txt" -cat "$MOUNTPOINT/keep-cache.txt" > /dev/null -cat "$MOUNTPOINT/keep-cache.txt" > /dev/null -printf "cache-after" > "$MOUNTPOINT/keep-cache.txt" -KEEP_CACHE_CONTENT=$(cat "$MOUNTPOINT/keep-cache.txt") -if [ "$KEEP_CACHE_CONTENT" != "cache-after" ]; then - echo "FAILED: keep-cache.txt content is stale after overwrite: $KEEP_CACHE_CONTENT" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi -truncate -s 5 "$MOUNTPOINT/keep-cache.txt" -KEEP_CACHE_CONTENT=$(cat "$MOUNTPOINT/keep-cache.txt") -if [ "$KEEP_CACHE_CONTENT" != "cache" ]; then - echo "FAILED: keep-cache.txt content is stale after truncate: $KEEP_CACHE_CONTENT" - kill $MOUNT_PID 2>/dev/null || true - wait $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Unmount -fusermount -u "$MOUNTPOINT" - -# Wait for mount process to exit -wait $MOUNT_PID 2>/dev/null || true - -echo "OK" diff --git a/cli/tests/test-init.sh b/cli/tests/test-init.sh deleted file mode 100755 index 7c669f71..00000000 --- a/cli/tests/test-init.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/sh -set -e - -echo -n "TEST init... " - -TEST_AGENT_ID="test-agent" - -# Cleanup any existing test database (not the entire .agentfs directory!) -rm -f ".agentfs/${TEST_AGENT_ID}.db" ".agentfs/${TEST_AGENT_ID}.db-shm" ".agentfs/${TEST_AGENT_ID}.db-wal" - -# Test: Run init command with specific ID -if ! output=$(cargo run -- init "$TEST_AGENT_ID" 2>&1); then - echo "FAILED: init command failed" - echo "Output was: $output" - exit 1 -fi - -# Check that .agentfs directory was created -if [ ! -d .agentfs ]; then - echo "FAILED: .agentfs directory was not created" - echo "Output was: $output" - exit 1 -fi - -# Check that the database file was created in .agentfs -if [ ! -f ".agentfs/$TEST_AGENT_ID.db" ]; then - echo "FAILED: agent database was not created in .agentfs directory" - echo "Output was: $output" - exit 1 -fi - -# Check that output contains success message with .agentfs path -echo "$output" | grep -q "Created agent filesystem: .agentfs/$TEST_AGENT_ID.db" || { - echo "FAILED: Expected success message not found in output" - echo "Output was: $output" - rm -f ".agentfs/${TEST_AGENT_ID}.db" ".agentfs/${TEST_AGENT_ID}.db-shm" ".agentfs/${TEST_AGENT_ID}.db-wal" - exit 1 -} - -# Test: Running init again should fail without --force -if cargo run -- init "$TEST_AGENT_ID" 2>&1 | grep -q "already exists"; then - : # Expected behavior -else - echo "FAILED: init should fail when agent database already exists" - rm -f ".agentfs/${TEST_AGENT_ID}.db" ".agentfs/${TEST_AGENT_ID}.db-shm" ".agentfs/${TEST_AGENT_ID}.db-wal" - exit 1 -fi - -# Test: Running init with --force should succeed -if ! output=$(cargo run -- init "$TEST_AGENT_ID" --force 2>&1); then - echo "FAILED: init --force command failed" - echo "Output was: $output" - rm -f ".agentfs/${TEST_AGENT_ID}.db" ".agentfs/${TEST_AGENT_ID}.db-shm" ".agentfs/${TEST_AGENT_ID}.db-wal" - exit 1 -fi - -# Check that output contains success message -echo "$output" | grep -q "Created agent filesystem: .agentfs/$TEST_AGENT_ID.db" || { - echo "FAILED: Expected success message not found in init --force output" - echo "Output was: $output" - rm -f ".agentfs/${TEST_AGENT_ID}.db" ".agentfs/${TEST_AGENT_ID}.db-shm" ".agentfs/${TEST_AGENT_ID}.db-wal" - exit 1 -} - -# Cleanup test database only -rm -f ".agentfs/${TEST_AGENT_ID}.db" ".agentfs/${TEST_AGENT_ID}.db-shm" ".agentfs/${TEST_AGENT_ID}.db-wal" - -echo "OK" diff --git a/cli/tests/test-mount.sh b/cli/tests/test-mount.sh deleted file mode 100755 index a5b2906e..00000000 --- a/cli/tests/test-mount.sh +++ /dev/null @@ -1,140 +0,0 @@ -#!/bin/sh -set -e - -echo -n "TEST mount... " - -TEST_AGENT_ID="test-mount-agent" -MOUNTPOINT="/tmp/agentfs-test-mount-$$" - -cleanup() { - # Unmount if mounted - fusermount -u "$MOUNTPOINT" 2>/dev/null || true - # Remove mountpoint - rmdir "$MOUNTPOINT" 2>/dev/null || true - # Remove test database - rm -f ".agentfs/${TEST_AGENT_ID}.db" ".agentfs/${TEST_AGENT_ID}.db-shm" ".agentfs/${TEST_AGENT_ID}.db-wal" -} - -# Ensure cleanup on exit -trap cleanup EXIT - -# Clean up any existing test artifacts -cleanup - -# Initialize the database -cargo run -- init "$TEST_AGENT_ID" > /dev/null 2>&1 - -# Create mountpoint -mkdir -p "$MOUNTPOINT" - -# Mount in foreground mode (background it ourselves so we can control it) -cargo run -- mount ".agentfs/${TEST_AGENT_ID}.db" "$MOUNTPOINT" --foreground & -MOUNT_PID=$! - -# Wait for mount to be ready (check if mountpoint has different device ID) -MAX_WAIT=10 -WAITED=0 -while [ $WAITED -lt $MAX_WAIT ]; do - if mountpoint -q "$MOUNTPOINT" 2>/dev/null; then - break - fi - sleep 0.5 - WAITED=$((WAITED + 1)) -done - -if ! mountpoint -q "$MOUNTPOINT" 2>/dev/null; then - echo "FAILED: mount did not become ready in time" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test that 'agentfs mount' (no args) lists our mount -if ! cargo run -- mount 2>/dev/null | grep -q "$MOUNTPOINT"; then - echo "FAILED: 'agentfs mount' did not list our mountpoint" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Write a file through the FUSE mount -echo "hello from fuse mount" > "$MOUNTPOINT/hello.txt" - -# Read it back -CONTENT=$(cat "$MOUNTPOINT/hello.txt") - -if [ "$CONTENT" != "hello from fuse mount" ]; then - echo "FAILED: file content mismatch" - echo "Expected: hello from fuse mount" - echo "Got: $CONTENT" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test mkdir -mkdir "$MOUNTPOINT/testdir" -if [ ! -d "$MOUNTPOINT/testdir" ]; then - echo "FAILED: mkdir did not create directory" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test creating file in subdirectory -echo "nested file" > "$MOUNTPOINT/testdir/nested.txt" -NESTED_CONTENT=$(cat "$MOUNTPOINT/testdir/nested.txt") - -if [ "$NESTED_CONTENT" != "nested file" ]; then - echo "FAILED: nested file content mismatch" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test symlink creation -ln -s nested.txt "$MOUNTPOINT/testdir/link_to_nested" -if [ ! -L "$MOUNTPOINT/testdir/link_to_nested" ]; then - echo "FAILED: symlink was not created" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test reading symlink target -LINK_TARGET=$(readlink "$MOUNTPOINT/testdir/link_to_nested") -if [ "$LINK_TARGET" != "nested.txt" ]; then - echo "FAILED: symlink target mismatch" - echo "Expected: nested.txt" - echo "Got: $LINK_TARGET" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test following symlink to read file -LINKED_CONTENT=$(cat "$MOUNTPOINT/testdir/link_to_nested") -if [ "$LINKED_CONTENT" != "nested file" ]; then - echo "FAILED: reading through symlink failed" - echo "Expected: nested file" - echo "Got: $LINKED_CONTENT" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test symlink to directory -ln -s testdir "$MOUNTPOINT/link_to_testdir" -if [ ! -L "$MOUNTPOINT/link_to_testdir" ]; then - echo "FAILED: symlink to directory was not created" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test accessing file through directory symlink -DIR_LINKED_CONTENT=$(cat "$MOUNTPOINT/link_to_testdir/nested.txt") -if [ "$DIR_LINKED_CONTENT" != "nested file" ]; then - echo "FAILED: reading through directory symlink failed" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Unmount -fusermount -u "$MOUNTPOINT" - -# Wait for mount process to exit -wait $MOUNT_PID 2>/dev/null || true - -echo "OK" diff --git a/cli/tests/test-overlay-delta-in-base-dir.sh b/cli/tests/test-overlay-delta-in-base-dir.sh deleted file mode 100755 index 7dc85531..00000000 --- a/cli/tests/test-overlay-delta-in-base-dir.sh +++ /dev/null @@ -1,165 +0,0 @@ -#!/bin/sh -set -e - -echo -n "TEST overlay readdir/unlink for delta files in base directories... " - -TEST_AGENT_ID="test-overlay-delta-in-base-dir-agent" -MOUNTPOINT="/tmp/agentfs-test-overlay-mount-$$" -BASEDIR="/tmp/agentfs-test-overlay-base-$$" - -cleanup() { - # Unmount if mounted - fusermount -u "$MOUNTPOINT" 2>/dev/null || true - # Remove directories - rm -rf "$MOUNTPOINT" "$BASEDIR" 2>/dev/null || true - # Remove test database - rm -f ".agentfs/${TEST_AGENT_ID}.db" ".agentfs/${TEST_AGENT_ID}.db-shm" ".agentfs/${TEST_AGENT_ID}.db-wal" -} - -# Ensure cleanup on exit -trap cleanup EXIT - -# Clean up any existing test artifacts -cleanup - -# Create base directory with a subdirectory (simulating .git) -mkdir -p "$BASEDIR/.git" -echo "[core]" > "$BASEDIR/.git/config" -echo "ref: refs/heads/main" > "$BASEDIR/.git/HEAD" - -# Initialize the database with --base for overlay -if ! output=$(cargo run -- init "$TEST_AGENT_ID" --base "$BASEDIR" 2>&1); then - echo "FAILED: init with --base failed" - echo "Output was: $output" - exit 1 -fi - -# Create mountpoint -mkdir -p "$MOUNTPOINT" - -# Mount in foreground mode (background it ourselves so we can control it) -cargo run -- mount ".agentfs/${TEST_AGENT_ID}.db" "$MOUNTPOINT" --foreground & -MOUNT_PID=$! - -# Wait for mount to be ready -MAX_WAIT=10 -WAITED=0 -while [ $WAITED -lt $MAX_WAIT ]; do - if mountpoint -q "$MOUNTPOINT" 2>/dev/null; then - break - fi - sleep 0.5 - WAITED=$((WAITED + 1)) -done - -if ! mountpoint -q "$MOUNTPOINT" 2>/dev/null; then - echo "FAILED: mount did not become ready in time" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Verify base directory structure is visible -if [ ! -d "$MOUNTPOINT/.git" ]; then - echo "FAILED: base .git directory not visible through overlay" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -if [ ! -f "$MOUNTPOINT/.git/config" ]; then - echo "FAILED: base .git/config file not visible through overlay" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Create a new file in the base subdirectory through the overlay -# This triggers ensure_parent_dirs which creates .git in delta with origin mapping -echo "lock content" > "$MOUNTPOINT/.git/index.lock" - -# Verify the file was created -if [ ! -f "$MOUNTPOINT/.git/index.lock" ]; then - echo "FAILED: could not create index.lock in .git directory" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Verify readdir shows both base and delta files -# This is the first bug: delta files in base directories were invisible in readdir -LS_OUTPUT=$(ls "$MOUNTPOINT/.git") -if ! echo "$LS_OUTPUT" | grep -q "index.lock"; then - echo "FAILED: readdir does not show delta file index.lock" - echo "ls output was: $LS_OUTPUT" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -if ! echo "$LS_OUTPUT" | grep -q "config"; then - echo "FAILED: readdir does not show base file config" - echo "ls output was: $LS_OUTPUT" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -if ! echo "$LS_OUTPUT" | grep -q "HEAD"; then - echo "FAILED: readdir does not show base file HEAD" - echo "ls output was: $LS_OUTPUT" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Delete the delta file -# This is the second bug: unlink failed for delta files in base directories -rm "$MOUNTPOINT/.git/index.lock" - -# Verify the file is actually deleted -if [ -f "$MOUNTPOINT/.git/index.lock" ]; then - echo "FAILED: index.lock still exists after deletion" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Verify readdir no longer shows it -LS_OUTPUT_AFTER=$(ls "$MOUNTPOINT/.git") -if echo "$LS_OUTPUT_AFTER" | grep -q "index.lock"; then - echo "FAILED: readdir still shows index.lock after deletion" - echo "ls output was: $LS_OUTPUT_AFTER" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Base files should still be visible -if ! echo "$LS_OUTPUT_AFTER" | grep -q "config"; then - echo "FAILED: base file config disappeared after delta file deletion" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Test creating and deleting a subdirectory in a base directory -mkdir "$MOUNTPOINT/.git/objects" -if [ ! -d "$MOUNTPOINT/.git/objects" ]; then - echo "FAILED: could not create objects subdirectory in .git" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Verify readdir shows the new directory -LS_WITH_DIR=$(ls "$MOUNTPOINT/.git") -if ! echo "$LS_WITH_DIR" | grep -q "objects"; then - echo "FAILED: readdir does not show delta directory objects" - echo "ls output was: $LS_WITH_DIR" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Remove the directory (rmdir) -rmdir "$MOUNTPOINT/.git/objects" -if [ -d "$MOUNTPOINT/.git/objects" ]; then - echo "FAILED: objects directory still exists after rmdir" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Unmount -fusermount -u "$MOUNTPOINT" -wait $MOUNT_PID 2>/dev/null || true - -echo "OK" diff --git a/cli/tests/test-overlay-whiteout.sh b/cli/tests/test-overlay-whiteout.sh deleted file mode 100755 index db5556de..00000000 --- a/cli/tests/test-overlay-whiteout.sh +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/sh -set -e - -echo -n "TEST overlay whiteout persistence... " - -TEST_AGENT_ID="test-overlay-whiteout-agent" -MOUNTPOINT="/tmp/agentfs-test-overlay-mount-$$" -BASEDIR="/tmp/agentfs-test-overlay-base-$$" - -cleanup() { - # Unmount if mounted - fusermount -u "$MOUNTPOINT" 2>/dev/null || true - # Remove directories - rm -rf "$MOUNTPOINT" "$BASEDIR" 2>/dev/null || true - # Remove test database - rm -f ".agentfs/${TEST_AGENT_ID}.db" ".agentfs/${TEST_AGENT_ID}.db-shm" ".agentfs/${TEST_AGENT_ID}.db-wal" -} - -# Ensure cleanup on exit -trap cleanup EXIT - -# Clean up any existing test artifacts -cleanup - -# Create base directory with a test file -mkdir -p "$BASEDIR" -echo "original content" > "$BASEDIR/testfile.txt" - -# Initialize the database with --base for overlay -if ! output=$(cargo run -- init "$TEST_AGENT_ID" --base "$BASEDIR" 2>&1); then - echo "FAILED: init with --base failed" - echo "Output was: $output" - exit 1 -fi - -# Create mountpoint -mkdir -p "$MOUNTPOINT" - -# Mount in foreground mode (background it ourselves so we can control it) -cargo run -- mount ".agentfs/${TEST_AGENT_ID}.db" "$MOUNTPOINT" --foreground & -MOUNT_PID=$! - -# Wait for mount to be ready -MAX_WAIT=10 -WAITED=0 -while [ $WAITED -lt $MAX_WAIT ]; do - if mountpoint -q "$MOUNTPOINT" 2>/dev/null; then - break - fi - sleep 0.5 - WAITED=$((WAITED + 1)) -done - -if ! mountpoint -q "$MOUNTPOINT" 2>/dev/null; then - echo "FAILED: mount did not become ready in time" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Verify base file is visible through overlay -if [ ! -f "$MOUNTPOINT/testfile.txt" ]; then - echo "FAILED: base file not visible through overlay" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -CONTENT=$(cat "$MOUNTPOINT/testfile.txt") -if [ "$CONTENT" != "original content" ]; then - echo "FAILED: base file content mismatch" - echo "Expected: original content" - echo "Got: $CONTENT" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Delete the file through the overlay -rm "$MOUNTPOINT/testfile.txt" - -# Verify file is deleted -if [ -f "$MOUNTPOINT/testfile.txt" ]; then - echo "FAILED: file still exists after deletion" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Unmount -fusermount -u "$MOUNTPOINT" -wait $MOUNT_PID 2>/dev/null || true - -# Remount to test persistence -cargo run -- mount ".agentfs/${TEST_AGENT_ID}.db" "$MOUNTPOINT" --foreground & -MOUNT_PID=$! - -# Wait for mount to be ready -WAITED=0 -while [ $WAITED -lt $MAX_WAIT ]; do - if mountpoint -q "$MOUNTPOINT" 2>/dev/null; then - break - fi - sleep 0.5 - WAITED=$((WAITED + 1)) -done - -if ! mountpoint -q "$MOUNTPOINT" 2>/dev/null; then - echo "FAILED: remount did not become ready in time" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Verify file is still deleted after remount (whiteout was persisted) -if [ -f "$MOUNTPOINT/testfile.txt" ]; then - echo "FAILED: deleted file reappeared after remount (whiteout not persisted)" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Verify base file still exists in original location (untouched) -if [ ! -f "$BASEDIR/testfile.txt" ]; then - echo "FAILED: base file was modified (should be untouched)" - kill $MOUNT_PID 2>/dev/null || true - exit 1 -fi - -# Unmount -fusermount -u "$MOUNTPOINT" -wait $MOUNT_PID 2>/dev/null || true - -echo "OK" diff --git a/cli/tests/test-run-bash.sh b/cli/tests/test-run-bash.sh deleted file mode 100755 index 564b0431..00000000 --- a/cli/tests/test-run-bash.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh -set -e - -echo -n "TEST interactive bash session... " - -# Run bash session in overlay: write a file and read it back -# The current directory becomes copy-on-write with the overlay sandbox -output=$(cargo run -- run /bin/bash -c ' -echo "hello from agent" > hello.txt -cat hello.txt -' 2>&1) - -# Verify we got the expected output -echo "$output" | grep -q "hello from agent" || { - echo "FAILED" - echo "$output" - exit 1 -} - -# Verify the file was NOT written to the host (it's in the delta layer) -if [ -f "hello.txt" ]; then - echo "FAILED: hello.txt should not exist on host filesystem" - rm -f hello.txt - exit 1 -fi - -echo "OK" diff --git a/cli/tests/test-run-experimental-syscalls.sh b/cli/tests/test-run-experimental-syscalls.sh deleted file mode 100755 index 16e9729b..00000000 --- a/cli/tests/test-run-experimental-syscalls.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/sh -# -# Test syscalls through agentfs run --experimental-sandbox (ptrace-based). -# -# This tests AgentFS file operations using the ptrace-based sandbox -# where files are stored in the AgentFS database at /agent. -# -set -e - -echo -n "TEST syscalls (agentfs run --experimental-sandbox)... " - -DIR="$(dirname "$0")" - -# Compile the test program -make -C "$DIR/syscall" clean > /dev/null 2>&1 -make -C "$DIR/syscall" > /dev/null 2>&1 - -TEST_DB="agent.db" - -# Clean up any existing test database -rm -f "$TEST_DB" "${TEST_DB}-wal" "${TEST_DB}-shm" - -# Initialize the database -cargo run -- init > /dev/null 2>&1 - -# Populate with test files using experimental sandbox -# The experimental sandbox mounts agent.db at /agent -cargo run -- run --experimental-sandbox /bin/bash -c 'echo "Hello from virtual FD!" > /agent/test.txt' > /dev/null 2>&1 - -# Create existing.txt for the append test -cargo run -- run --experimental-sandbox /bin/bash -c 'echo -n "original content" > /agent/existing.txt' > /dev/null 2>&1 - -# Note: The nested directory test (test_append_nested) is skipped for experimental sandbox -# because it tests FUSE overlay COW behavior where parent dirs need to be created in delta. -# The experimental sandbox has no base layer, so this scenario doesn't apply. - -# Run the syscall tests using the experimental ptrace-based sandbox -if ! output=$(cargo run -- run --experimental-sandbox "$DIR/syscall/test-syscalls" /agent 2>&1); then - echo "FAILED" - echo "Output was: $output" - rm -f "$TEST_DB" "${TEST_DB}-wal" "${TEST_DB}-shm" - exit 1 -fi - -echo "$output" | grep -q "All tests passed!" || { - echo "FAILED: 'All tests passed!' not found" - echo "Output was: $output" - rm -f "$TEST_DB" "${TEST_DB}-wal" "${TEST_DB}-shm" - exit 1 -} - -# Verify output file was created (by reading it back) -if ! output=$(cargo run -- run --experimental-sandbox /bin/cat /agent/output.txt 2>&1); then - echo "FAILED: output.txt was not created or readable" - rm -f "$TEST_DB" "${TEST_DB}-wal" "${TEST_DB}-shm" - exit 1 -fi - -rm -f "$TEST_DB" "${TEST_DB}-wal" "${TEST_DB}-shm" - -echo "OK" diff --git a/cli/tests/test-run-git.sh b/cli/tests/test-run-git.sh deleted file mode 100755 index 972af49e..00000000 --- a/cli/tests/test-run-git.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh -set -e - -echo -n "TEST git init and commit in overlay... " - -# Clean up any previous test directory -rm -rf test-git-repo - -# Run git operations in overlay: init, add, commit -output=$(cargo run -- run /bin/bash -c ' -mkdir test-git-repo -cd test-git-repo -git init -echo "hello" > hello.txt -git add hello.txt -git commit -m "Initial commit" -git log --oneline -' 2>&1) - -# Verify we got a successful commit (git log shows commit hash and message) -echo "$output" | grep -q "Initial commit" || { - echo "FAILED" - echo "$output" - exit 1 -} - -# Verify the directory was NOT written to the host (it's in the delta layer) -if [ -d "test-git-repo" ]; then - echo "FAILED: test-git-repo should not exist on host filesystem" - rm -rf test-git-repo - exit 1 -fi - -echo "OK" diff --git a/cli/.gitignore b/crates/agentfs-cli/.gitignore similarity index 100% rename from cli/.gitignore rename to crates/agentfs-cli/.gitignore diff --git a/cli/CONTRIBUTING.md b/crates/agentfs-cli/CONTRIBUTING.md similarity index 100% rename from cli/CONTRIBUTING.md rename to crates/agentfs-cli/CONTRIBUTING.md diff --git a/crates/agentfs-cli/Cargo.toml b/crates/agentfs-cli/Cargo.toml new file mode 100644 index 00000000..7f909cdc --- /dev/null +++ b/crates/agentfs-cli/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "agentfs-cli" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "Command-line interface for AgentFS" + +[lib] +name = "agentfs_cli" +path = "src/lib.rs" + +[[bin]] +name = "agentfs" +path = "src/main.rs" + +[features] +default = [] +strict = [] + +[lints] +workspace = true + +[dependencies] +agentfs-core = { workspace = true } +agentfs-mount = { workspace = true } +tokio = { workspace = true } +clap = { workspace = true } +anyhow = { workspace = true } +hex = { workspace = true } +sha1 = { workspace = true } +turso = { workspace = true } +serde = { workspace = true } +parking_lot = { workspace = true } +clap_complete = { workspace = true } +dirs = { workspace = true } +serde_json = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +chrono = { workspace = true } + +# MCP Server support +base64 = { workspace = true } + +uuid = { workspace = true } + +# Unix dependencies +[target.'cfg(unix)'.dependencies] +libc = { workspace = true } + +[dev-dependencies] +tempfile = { workspace = true } diff --git a/crates/agentfs-cli/build.rs b/crates/agentfs-cli/build.rs new file mode 100644 index 00000000..091b2c1c --- /dev/null +++ b/crates/agentfs-cli/build.rs @@ -0,0 +1,50 @@ +use std::path::Path; +use std::process::Command; + +fn main() { + let repo_root = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .expect("agentfs-cli should live under crates/agentfs-cli"); + + // Capture git version from tags for --version flag + // Rerun if git HEAD changes (new commits or tags) + let git_dir = repo_root.join(".git"); + println!("cargo:rerun-if-changed={}", git_dir.join("HEAD").display()); + println!( + "cargo:rerun-if-changed={}", + git_dir.join("refs/tags").display() + ); + // .git/HEAD only changes on branch switches. Commits move the branch ref + // HEAD points at, so track that file too (and packed-refs, where the ref + // lands after `git pack-refs`) or --version reports stale git metadata. + if let Ok(head) = std::fs::read_to_string(git_dir.join("HEAD")) { + if let Some(head_ref) = head.strip_prefix("ref: ") { + println!( + "cargo:rerun-if-changed={}", + git_dir.join(head_ref.trim()).display() + ); + } + } + println!( + "cargo:rerun-if-changed={}", + git_dir.join("packed-refs").display() + ); + + let version = Command::new("git") + .current_dir(repo_root) + .args(["describe", "--tags", "--always", "--dirty"]) + .output() + .ok() + .and_then(|output| { + if output.status.success() { + String::from_utf8(output.stdout).ok() + } else { + None + } + }) + .map(|v| v.trim().to_string()) + .unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string()); + + println!("cargo:rustc-env=AGENTFS_VERSION={}", version); +} diff --git a/cli/perf/simulate-npm-workload.sh b/crates/agentfs-cli/perf/simulate-npm-workload.sh similarity index 100% rename from cli/perf/simulate-npm-workload.sh rename to crates/agentfs-cli/perf/simulate-npm-workload.sh diff --git a/cli/perf/syscall/Makefile b/crates/agentfs-cli/perf/syscall/Makefile similarity index 100% rename from cli/perf/syscall/Makefile rename to crates/agentfs-cli/perf/syscall/Makefile diff --git a/cli/perf/syscall/hello.txt b/crates/agentfs-cli/perf/syscall/hello.txt similarity index 100% rename from cli/perf/syscall/hello.txt rename to crates/agentfs-cli/perf/syscall/hello.txt diff --git a/cli/perf/syscall/perf-open-close.c b/crates/agentfs-cli/perf/syscall/perf-open-close.c similarity index 100% rename from cli/perf/syscall/perf-open-close.c rename to crates/agentfs-cli/perf/syscall/perf-open-close.c diff --git a/cli/perf/syscall/perf-statx.c b/crates/agentfs-cli/perf/syscall/perf-statx.c similarity index 100% rename from cli/perf/syscall/perf-statx.c rename to crates/agentfs-cli/perf/syscall/perf-statx.c diff --git a/cli/perf/syscall/run.sh b/crates/agentfs-cli/perf/syscall/run.sh similarity index 97% rename from cli/perf/syscall/run.sh rename to crates/agentfs-cli/perf/syscall/run.sh index 18841ede..5b208ca5 100755 --- a/cli/perf/syscall/run.sh +++ b/crates/agentfs-cli/perf/syscall/run.sh @@ -10,9 +10,10 @@ set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" CLI_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" +REPO_ROOT="$(cd "$CLI_DIR/../.." && pwd)" TEST_FILE="$SCRIPT_DIR/hello.txt" ITERATIONS="${1:-100000}" -AGENTFS="$CLI_DIR/target/release/agentfs" +AGENTFS="$REPO_ROOT/target/release/agentfs" # Build benchmarks if needed make -C "$SCRIPT_DIR" -s diff --git a/cli/scripts/fmt-imports b/crates/agentfs-cli/scripts/fmt-imports similarity index 100% rename from cli/scripts/fmt-imports rename to crates/agentfs-cli/scripts/fmt-imports diff --git a/cli/scripts/mount.fuse.agentfs b/crates/agentfs-cli/scripts/mount.fuse.agentfs similarity index 100% rename from cli/scripts/mount.fuse.agentfs rename to crates/agentfs-cli/scripts/mount.fuse.agentfs diff --git a/cli/src/cmd/clone.rs b/crates/agentfs-cli/src/cmd/clone.rs similarity index 86% rename from cli/src/cmd/clone.rs rename to crates/agentfs-cli/src/cmd/clone.rs index d6abc448..ce8a2933 100644 --- a/cli/src/cmd/clone.rs +++ b/crates/agentfs-cli/src/cmd/clone.rs @@ -6,7 +6,7 @@ //! `git clone --no-checkout` through a temporary mount (pack files are a few //! large sequential writes), then streams the worktree content out of the //! object database: a producer thread parses `git ls-tree` + `git cat-file -//! --batch` output while an [`agentfs_sdk::ImportSession`] consumer bulk +//! --batch` output while an [`agentfs_core::ImportSession`] consumer bulk //! imports each chunk (large multi-inode transactions), so blob decoding //! overlaps SQLite writes instead of buffering every blob in memory first. //! Finally it fabricates a git index whose cached stat data matches exactly @@ -25,12 +25,17 @@ use std::process::{Command, Stdio}; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; -use agentfs_sdk::{AgentFSOptions, FileSystem, ImportEntry, ImportOptions, ImportedEntry}; +use agentfs_core::{AgentFSOptions, FileSystem, ImportEntry, ImportOptions, ImportedEntry}; use anyhow::{bail, Context, Result}; use sha1::{Digest, Sha1}; use crate::cmd::init::open_agentfs; -use crate::mount::{mount_fs, MountBackend, MountOpts}; +use crate::opts::MountBackend; +use agentfs_mount::supervise::{ + run_supervised_task, set_parent_death_signal_std, supervise_command, ChildOutcome, + SupervisedTaskOutcome, +}; +use agentfs_mount::{mount_fs, MountOpts}; const S_IFDIR: u32 = 0o040000; const MODE_FILE: u32 = 0o100644; @@ -64,7 +69,7 @@ pub async fn handle_clone_command( .unwrap_or_else(|_| AgentFSOptions::with_path(&id_or_path)); let agentfs = open_agentfs(options) .await - .with_context(|| format!("failed to open AgentFS database: {id_or_path}"))?; + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; let agent = agentfs.fs.clone(); let fs: Arc = Arc::new(agentfs.fs); @@ -74,7 +79,7 @@ pub async fn handle_clone_command( let mount_opts = MountOpts { mountpoint: mountpoint.clone(), - backend, + backend: backend.into(), fsname: format!("agentfs:{id_or_path}"), uid: None, gid: None, @@ -86,12 +91,27 @@ pub async fn handle_clone_command( }; let mount_handle = mount_fs(fs, mount_opts).await?; - let result = clone_into_mount(&agent, &mountpoint, &source, &repo_name, verify).await; - - drop(mount_handle); + let result = run_supervised_task( + mount_handle, + clone_into_mount(&agent, &mountpoint, &source, &repo_name, verify), + ) + .await; let _ = std::fs::remove_dir_all(&mountpoint); - let summary = result?; + let summary = match result { + Ok(SupervisedTaskOutcome::Completed(summary)) => summary, + Ok(SupervisedTaskOutcome::Interrupted(signo)) => { + crate::profiling::emit_cli_report(); + std::process::exit(128 + signo); + } + Err(error) => { + if let Some(interrupted) = error.downcast_ref::() { + crate::profiling::emit_cli_report(); + std::process::exit(128 + interrupted.0); + } + return Err(error); + } + }; eprintln!( "Cloned {} into {} ({} files, {} bytes imported)", source, id_or_path, summary.files, summary.bytes @@ -99,19 +119,30 @@ pub async fn handle_clone_command( Ok(()) } +#[derive(Debug)] +struct InterruptedSignal(i32); + +impl std::fmt::Display for InterruptedSignal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "interrupted by signal {}", self.0) + } +} + +impl std::error::Error for InterruptedSignal {} + struct CloneSummary { files: usize, bytes: u64, } async fn clone_into_mount( - agent: &agentfs_sdk::filesystem::AgentFS, + agent: &agentfs_core::fs::AgentFS, mountpoint: &Path, source: &str, repo_name: &str, verify: bool, ) -> Result { - let timings = std::env::var("AGENTFS_CLONE_TIMINGS").is_ok_and(|v| v == "1"); + let timings = crate::config::clone_timings_enabled(); let mut stage_start = std::time::Instant::now(); let mut stage = |name: &str| { if timings { @@ -128,7 +159,8 @@ async fn clone_into_mount( run_git( Path::new("."), &["clone", "--no-checkout", "--quiet", source, repo_dir_str], - )?; + ) + .await?; stage("git-clone-no-checkout"); let head = run_git_capture(&repo_dir, &["rev-parse", "--verify", "--quiet", "HEAD"]).ok(); @@ -220,25 +252,28 @@ fn derive_repo_name(source: &str) -> Result { Ok(last.trim_end_matches(".git").to_string()) } -fn run_git(cwd: &Path, args: &[&str]) -> Result<()> { - let status = Command::new("git") - .args(args) - .current_dir(cwd) - .status() - .context("failed to run git")?; - if !status.success() { - bail!("git {} failed with {status}", args.join(" ")); +async fn run_git(cwd: &Path, args: &[&str]) -> Result<()> { + let mut command = tokio::process::Command::new("git"); + command.args(args).current_dir(cwd); + match supervise_command(command) + .await + .context("failed to run git")? + { + ChildOutcome::Exited(status) => { + if !status.success() { + bail!("git {} failed with {status}", args.join(" ")); + } + } + ChildOutcome::Interrupted(signo) => return Err(InterruptedSignal(signo).into()), } Ok(()) } fn run_git_capture(repo: &Path, args: &[&str]) -> Result { - let output = Command::new("git") - .arg("-C") - .arg(repo) - .args(args) - .output() - .context("failed to run git")?; + let mut command = Command::new("git"); + command.arg("-C").arg(repo).args(args); + set_parent_death_signal_std(&mut command); + let output = command.output().context("failed to run git")?; if !output.status.success() { bail!( "git {} failed with {}: {}", @@ -251,12 +286,13 @@ fn run_git_capture(repo: &Path, args: &[&str]) -> Result { } fn ls_tree(repo: &Path) -> Result> { - let output = Command::new("git") + let mut command = Command::new("git"); + command .arg("-C") .arg(repo) - .args(["ls-tree", "-r", "-z", "HEAD"]) - .output() - .context("failed to run git ls-tree")?; + .args(["ls-tree", "-r", "-z", "HEAD"]); + set_parent_death_signal_std(&mut command); + let output = command.output().context("failed to run git ls-tree")?; if !output.status.success() { bail!( "git ls-tree failed: {}", @@ -343,13 +379,16 @@ fn spawn_blob_producer( refs.push((row.path.clone(), row.mode)); } - let mut child = Command::new("git") + let mut command = Command::new("git"); + command .arg("-C") .arg(&repo) .args(["cat-file", "--batch"]) .stdin(Stdio::piped()) .stdout(Stdio::piped()) - .stderr(Stdio::null()) + .stderr(Stdio::null()); + set_parent_death_signal_std(&mut command); + let mut child = command .spawn() .context("failed to spawn git cat-file --batch")?; let mut stdin = child.stdin.take().context("missing cat-file stdin")?; diff --git a/cli/src/cmd/completions.rs b/crates/agentfs-cli/src/cmd/completions.rs similarity index 75% rename from cli/src/cmd/completions.rs rename to crates/agentfs-cli/src/cmd/completions.rs index ca409a23..559b6e44 100644 --- a/cli/src/cmd/completions.rs +++ b/crates/agentfs-cli/src/cmd/completions.rs @@ -32,7 +32,7 @@ impl fmt::Display for Shell { impl Shell { /// Detect the current shell from the `SHELL` environment variable fn detect() -> Option { - let shell_path = std::env::var("SHELL").ok()?; + let shell_path = crate::config::current_shell_path()?; let shell_name = shell_path.rsplit('/').next()?; match shell_name { "bash" => Some(Shell::Bash), @@ -71,40 +71,25 @@ impl Shell { } } -pub fn handle_completions(command: CompletionsCommand) { +pub fn handle_completions(command: CompletionsCommand) -> anyhow::Result<()> { match command { CompletionsCommand::Install { shell } => { - let shell = match shell.or_else(Shell::detect) { - Some(s) => s, - None => { - eprintln!( - "Error: Could not detect current shell. Please specify a shell explicitly." - ); - std::process::exit(1) - } - }; - if let Err(err) = install(shell) { - eprintln!("Error: {err}"); - std::process::exit(1) - } + let shell = detect_or_specified(shell)?; + install(shell)?; } CompletionsCommand::Uninstall { shell } => { - let shell = match shell.or_else(Shell::detect) { - Some(s) => s, - None => { - eprintln!( - "Error: Could not detect current shell. Please specify a shell explicitly." - ); - std::process::exit(1) - } - }; - if let Err(err) = uninstall(shell) { - eprintln!("Error: {err}"); - std::process::exit(1) - } + let shell = detect_or_specified(shell)?; + uninstall(shell)?; } CompletionsCommand::Show => show(), } + Ok(()) +} + +fn detect_or_specified(shell: Option) -> anyhow::Result { + shell.or_else(Shell::detect).ok_or_else(|| { + anyhow::anyhow!("Could not detect current shell. Please specify a shell explicitly.") + }) } fn install(shell: Shell) -> io::Result<()> { @@ -206,3 +191,45 @@ fn show() { println!("Then restart your shell or source your config file."); } + +#[cfg(test)] +mod tests { + use super::Shell; + use clap::ValueEnum; + + #[test] + fn shell_enum_matches_clap_complete_builtins() { + let mut ours: Vec = Shell::value_variants() + .iter() + .map(|shell| shell.to_string()) + .collect(); + ours.sort(); + let mut builtins: Vec = clap_complete::env::Shells::builtins() + .names() + .map(str::to_string) + .collect(); + builtins.sort(); + assert_eq!( + ours, builtins, + "Shell enum drifted from clap_complete's COMPLETE env shells" + ); + } + + #[test] + fn completions_help_names_every_supported_shell() { + use clap::CommandFactory; + let cmd = crate::opts::Args::command(); + let about = cmd + .find_subcommand("completions") + .expect("completions subcommand") + .get_about() + .map(ToString::to_string) + .unwrap_or_default(); + for shell in Shell::value_variants() { + assert!( + about.contains(&shell.to_string()), + "completions help must name {shell}; got: {about}" + ); + } + } +} diff --git a/cli/src/cmd/exec.rs b/crates/agentfs-cli/src/cmd/exec.rs similarity index 55% rename from cli/src/cmd/exec.rs rename to crates/agentfs-cli/src/cmd/exec.rs index 36afbd68..5822b27a 100644 --- a/cli/src/cmd/exec.rs +++ b/crates/agentfs-cli/src/cmd/exec.rs @@ -4,14 +4,16 @@ //! filesystem to a temporary directory, runs a command with that as the //! working directory, and automatically unmounts when done. -use agentfs_sdk::{AgentFSOptions, EncryptionConfig, FileSystem, HostFS, OverlayFS}; +use agentfs_core::{AgentFSOptions, EncryptionConfig, FileSystem, HostFS, OverlayFS}; use anyhow::{Context, Result}; use std::path::PathBuf; use std::sync::Arc; use turso::value::Value; use crate::cmd::init::open_agentfs; -use crate::mount::{mount_fs, MountBackend, MountOpts}; +use crate::opts::MountBackend; +use agentfs_mount::supervise::{exit_code_for_spawn_error, exit_code_for_status, run_supervised}; +use agentfs_mount::{mount_fs, MountOpts}; /// Handle the exec command. /// @@ -33,7 +35,9 @@ pub async fn handle_exec_command( } // Open AgentFS - let agentfs = open_agentfs(opts).await?; + let agentfs = open_agentfs(opts) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; // Check for overlay configuration let fs: Arc = { @@ -83,7 +87,7 @@ pub async fn handle_exec_command( let mount_opts = MountOpts { mountpoint: mountpoint.clone(), - backend, + backend: backend.into(), fsname, uid: None, gid: None, @@ -99,80 +103,35 @@ pub async fn handle_exec_command( // Mount the filesystem let mount_handle = mount_fs(fs, mount_opts).await?; - let outcome = supervise_child(&command, &args, &mountpoint).await; + let mut child = tokio::process::Command::new(&command); + child.args(&args).current_dir(&mountpoint); + // The CLI's private spill dir is process-internal; children keep the + // user's TMPDIR. + crate::config::restore_original_tmpdir(&mut child); + let status = run_supervised(mount_handle, child).await; - // Unmount and remove the mountpoint even when the workload was - // interrupted, so no dead mount table entry or temp directory survives. - drop(mount_handle); let _ = std::fs::remove_dir_all(&mountpoint); - match outcome? { - ChildOutcome::Exited(status) => { - if !status.success() { - std::process::exit(status.code().unwrap_or(1)); + let status = match status { + Ok(status) => status, + Err(error) => { + // Missing / non-executable commands pass through as 127/126 like + // run's child exec path (VAL-CLI-019 exception); every other + // failure goes to the unified reporter. + if let Some(code) = error + .downcast_ref::() + .and_then(exit_code_for_spawn_error) + { + eprintln!("Error: Failed to execute: {}: {}", command.display(), error); + crate::profiling::emit_cli_report(); + std::process::exit(code); } - Ok(()) + return Err(error).with_context(|| format!("Failed to execute: {}", command.display())); } - ChildOutcome::Interrupted(signo) => std::process::exit(128 + signo), - } -} - -enum ChildOutcome { - Exited(std::process::ExitStatus), - Interrupted(i32), -} - -/// Run the workload while listening for termination signals. -/// -/// The default signal disposition would kill this process without running -/// `MountHandle`'s unmount, leaving a dead mount table entry and the child -/// orphaned but alive inside it. PR_SET_PDEATHSIG additionally guarantees the -/// child cannot outlive us even under SIGKILL, which no userspace handler can -/// intercept. -async fn supervise_child( - command: &std::path::Path, - args: &[String], - mountpoint: &std::path::Path, -) -> Result { - use tokio::signal::unix::{signal, SignalKind}; - - let mut cmd = tokio::process::Command::new(command); - cmd.args(args).current_dir(mountpoint); - #[cfg(target_os = "linux")] - unsafe { - cmd.pre_exec(|| { - if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) != 0 { - return Err(std::io::Error::last_os_error()); - } - // The parent may have died between fork and prctl. - if libc::getppid() == 1 { - libc::raise(libc::SIGKILL); - } - Ok(()) - }); - } - let mut child = cmd - .spawn() - .with_context(|| format!("Failed to execute: {}", command.display()))?; - - let mut sigterm = signal(SignalKind::terminate())?; - let mut sigint = signal(SignalKind::interrupt())?; - let mut sighup = signal(SignalKind::hangup())?; - let signo = tokio::select! { - status = child.wait() => return Ok(ChildOutcome::Exited(status?)), - _ = sigterm.recv() => libc::SIGTERM, - _ = sigint.recv() => libc::SIGINT, - _ = sighup.recv() => libc::SIGHUP, }; - - if let Some(pid) = child.id() { - unsafe { libc::kill(pid as i32, libc::SIGTERM) }; - } - if tokio::time::timeout(std::time::Duration::from_secs(5), child.wait()) - .await - .is_err() - { - let _ = child.kill().await; + if !status.success() { + crate::profiling::emit_cli_report(); + std::process::exit(exit_code_for_status(status)); } - Ok(ChildOutcome::Interrupted(signo)) + Ok(()) } diff --git a/cli/src/cmd/fs.rs b/crates/agentfs-cli/src/cmd/fs.rs similarity index 63% rename from cli/src/cmd/fs.rs rename to crates/agentfs-cli/src/cmd/fs.rs index 1e35fcbf..c8197f61 100644 --- a/cli/src/cmd/fs.rs +++ b/crates/agentfs-cli/src/cmd/fs.rs @@ -1,10 +1,10 @@ use std::collections::VecDeque; -use agentfs_sdk::{AgentFSOptions, EncryptionConfig}; +use agentfs_core::{AgentFSOptions, EncryptionConfig}; use anyhow::{Context, Result as AnyhowResult}; use turso::Value; -use crate::cmd::init::open_agentfs; +use crate::cmd::init::{finalize_readonly, open_agentfs}; const ROOT_INO: i64 = 1; const S_IFMT: u32 = 0o170000; @@ -27,15 +27,25 @@ pub async fn ls_filesystem( } eprintln!("Using agent: {}", id_or_path); - let agentfs = open_agentfs(options).await?; + let agentfs = open_agentfs(options) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; + let result = ls_opened(stdout, &agentfs, path).await; + finalize_readonly(&agentfs).await; + result +} + +async fn ls_opened( + stdout: &mut impl std::io::Write, + agentfs: &agentfs_core::AgentFS, + path: &str, +) -> AnyhowResult<()> { let conn = agentfs.get_connection().await?; - if path != "/" { - anyhow::bail!("Only root directory (/) is currently supported"); - } + let start_ino = resolve_directory_ino(&conn, path).await?; let mut queue: VecDeque<(i64, String)> = VecDeque::new(); - queue.push_back((ROOT_INO, String::new())); + queue.push_back((start_ino, String::new())); while let Some((parent_ino, prefix)) = queue.pop_front() { let query = format!( @@ -102,6 +112,42 @@ pub async fn ls_filesystem( Ok(()) } +/// Resolve a directory path (absolute or relative to `/`) to its inode. +async fn resolve_directory_ino(conn: &turso::Connection, path: &str) -> AnyhowResult { + let mut ino = ROOT_INO; + let mut mode = S_IFDIR; + + for component in path.split('/').filter(|component| !component.is_empty()) { + let mut rows = conn + .query( + "SELECT d.ino, i.mode FROM fs_dentry d + JOIN fs_inode i ON d.ino = i.ino + WHERE d.parent_ino = ? AND d.name = ?", + (ino, component), + ) + .await + .context("Failed to query directory entry")?; + let Some(row) = rows.next().await.context("Failed to fetch row")? else { + anyhow::bail!("Path not found: {}", path); + }; + ino = row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .with_context(|| format!("Corrupt dentry ino for {}", path))?; + mode = row + .get_value(1) + .ok() + .and_then(|v| v.as_integer().copied()) + .with_context(|| format!("Corrupt inode mode for {}", path))? as u32; + } + + if mode & S_IFMT != S_IFDIR { + anyhow::bail!("Not a directory: {}", path); + } + Ok(ino) +} + pub async fn cat_filesystem( stdout: &mut impl std::io::Write, id_or_path: String, @@ -115,15 +161,22 @@ pub async fn cat_filesystem( cipher: cipher.clone(), }); } - let agentfs = open_agentfs(options).await?; + let agentfs = open_agentfs(options) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; - match agentfs.fs.read_file(path).await? { - Some(file) => { - stdout.write_all(&file)?; - Ok(()) + let result: AnyhowResult<()> = async { + match agentfs.fs.read_file(path).await? { + Some(file) => { + stdout.write_all(&file)?; + Ok(()) + } + None => anyhow::bail!("File not found: {}", path), } - None => anyhow::bail!("File not found: {}", path), } + .await; + finalize_readonly(&agentfs).await; + result } pub async fn write_filesystem( @@ -139,8 +192,15 @@ pub async fn write_filesystem( cipher: cipher.clone(), }); } - let agentfs = open_agentfs(options).await?; + let agentfs = open_agentfs(options) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; + // Own created entries as the invoking user, matching mount-created files: + // uid/gid 0 would make a later chmod inside `agentfs exec` (running as the + // invoking uid) fail with EPERM. + // SAFETY: getuid/getgid are always safe + let (uid, gid) = unsafe { (libc::getuid(), libc::getgid()) }; let mut components = path.split("/").collect::>(); if !path.starts_with("/") { components.insert(0, ""); @@ -150,20 +210,25 @@ pub async fn write_filesystem( for i in 2..components.len() { let dir_path = components[0..i].join("/"); if agentfs.fs.stat(&dir_path).await?.is_none() { - agentfs.fs.mkdir(&dir_path, 0, 0).await?; + agentfs.fs.mkdir(&dir_path, uid, gid).await?; } } // Remove file if it exists (overwrite behavior) if agentfs.fs.stat(path).await?.is_some() { agentfs.fs.remove(path).await?; } - let (_, file) = agentfs.fs.create_file(path, S_IFREG | 0o644, 0, 0).await?; + let (_, file) = agentfs + .fs + .create_file(path, S_IFREG | 0o644, uid, gid) + .await?; file.pwrite(0, content.as_bytes()).await?; // Tier Four: writes go into the in-memory batcher first. This CLI is a - // one-shot operation — flush so the bytes are durable in SQLite before - // we drop the AgentFS, otherwise a subsequent process or `cat` against - // the same path would see only the pre-write state. - agentfs.fs.drain_all().await?; + // one-shot operation — finalize so the bytes are durable in SQLite before + // we drop the AgentFS (otherwise a subsequent process or `cat` against + // the same path would see only the pre-write state) and the database + // family is single-file again (invariant I1) instead of keeping the + // truncated 0-byte -wal a bare drain leaves behind. + agentfs.fs.finalize().await?; Ok(()) } @@ -205,8 +270,15 @@ pub async fn diff_filesystem(id_or_path: String) -> AnyhowResult<()> { let options = AgentFSOptions::resolve(&id_or_path)?; eprintln!("Using agent: {}", id_or_path); - let agent = open_agentfs(options).await?; + let agent = open_agentfs(options) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; + let result = diff_opened(&agent).await; + finalize_readonly(&agent).await; + result +} +async fn diff_opened(agent: &agentfs_core::AgentFS) -> AnyhowResult<()> { // Check if overlay is enabled let base_path = match agent.is_overlay_enabled().await? { Some(path) => path, @@ -276,7 +348,7 @@ pub async fn diff_filesystem(id_or_path: String) -> AnyhowResult<()> { #[cfg(test)] mod tests { - use agentfs_sdk::{AgentFS, AgentFSOptions, EncryptionConfig}; + use agentfs_core::{AgentFS, AgentFSOptions, EncryptionConfig}; use tempfile::NamedTempFile; use crate::cmd::fs::{cat_filesystem, ls_filesystem, write_filesystem}; @@ -407,6 +479,109 @@ f d/e/3.md ); } + #[tokio::test] + pub async fn ls_subdir_lists_only_subtree() { + let (agentfs, path, _file) = agentfs().await; + agentfs.fs.mkdir("a", 0, 0).await.unwrap(); + agentfs.fs.mkdir("a/b", 0, 0).await.unwrap(); + agentfs.fs.mkdir("a/c", 0, 0).await.unwrap(); + agentfs.fs.mkdir("d", 0, 0).await.unwrap(); + write_file(&agentfs.fs, "a/b/1.md", b"1", 0, 0) + .await + .unwrap(); + write_file(&agentfs.fs, "a/c/2.md", b"11", 0, 0) + .await + .unwrap(); + write_file(&agentfs.fs, "d/3.md", b"111", 0, 0) + .await + .unwrap(); + let mut buf = Vec::new(); + ls_filesystem(&mut buf, path.clone(), "/a", None) + .await + .unwrap(); + assert_eq!( + buf, + b"d b +d c +f b/1.md +f c/2.md +" + ); + + // Relative paths resolve the same as absolute ones. + let mut relative_buf = Vec::new(); + ls_filesystem(&mut relative_buf, path.clone(), "a/b", None) + .await + .unwrap(); + assert_eq!(relative_buf, b"f 1.md\n"); + } + + #[tokio::test] + pub async fn ls_missing_path_errors() { + let (_agentfs, path, _file) = agentfs().await; + let mut buf = Vec::new(); + let err = ls_filesystem(&mut buf, path, "/missing", None) + .await + .unwrap_err(); + assert!( + err.to_string().contains("Path not found"), + "unexpected error: {err}" + ); + } + + #[tokio::test] + pub async fn ls_file_path_errors() { + let (agentfs, path, _file) = agentfs().await; + write_file(&agentfs.fs, "file.md", b"1", 0, 0) + .await + .unwrap(); + let mut buf = Vec::new(); + let err = ls_filesystem(&mut buf, path, "/file.md", None) + .await + .unwrap_err(); + assert!( + err.to_string().contains("Not a directory"), + "unexpected error: {err}" + ); + } + + #[tokio::test] + pub async fn read_commands_leave_no_wal_sidecar() { + let (agentfs, path, _file) = agentfs().await; + write_file(&agentfs.fs, "test.md", b"1", 0, 0) + .await + .unwrap(); + drop(agentfs); + let wal = format!("{path}-wal"); + + let mut buf = Vec::new(); + ls_filesystem(&mut buf, path.clone(), "/", None) + .await + .unwrap(); + assert!( + !std::path::Path::new(&wal).exists(), + "fs ls must not leave a WAL sidecar" + ); + + let mut buf = Vec::new(); + cat_filesystem(&mut buf, path.clone(), "test.md", None) + .await + .unwrap(); + assert!( + !std::path::Path::new(&wal).exists(), + "fs cat must not leave a WAL sidecar" + ); + + let mut buf = Vec::new(); + cat_filesystem(&mut buf, path.clone(), "missing.md", None) + .await + .unwrap_err(); + assert!( + !std::path::Path::new(&wal).exists(), + "a failing fs cat must not leave a WAL sidecar" + ); + } + // Encryption tests #[tokio::test] @@ -445,6 +620,34 @@ f d/e/3.md assert_eq!(buf, b"f file1.txt\nf file2.txt\n"); } + #[tokio::test] + pub async fn write_filesystem_owns_entries_as_invoking_user() { + let (_agentfs, path, _file) = agentfs().await; + write_filesystem(path.clone(), "/subdir/owned.txt", "content", None) + .await + .unwrap(); + + // uid/gid 0 entries make a later chmod inside `agentfs exec` (running + // as the invoking uid) fail EPERM, unlike mount-created files. + // SAFETY: getuid/getgid are always safe + let (uid, gid) = unsafe { (libc::getuid(), libc::getgid()) }; + let reader = AgentFS::open(AgentFSOptions::with_path(path)) + .await + .unwrap(); + let file_stats = reader.fs.stat("/subdir/owned.txt").await.unwrap().unwrap(); + assert_eq!( + (file_stats.uid, file_stats.gid), + (uid, gid), + "fs write must create files owned by the invoking uid/gid" + ); + let dir_stats = reader.fs.stat("/subdir").await.unwrap().unwrap(); + assert_eq!( + (dir_stats.uid, dir_stats.gid), + (uid, gid), + "fs write must create intermediate directories owned by the invoking uid/gid" + ); + } + #[tokio::test] pub async fn encrypted_write_filesystem() { let (_agentfs, path, _file) = encrypted_agentfs().await; @@ -467,7 +670,7 @@ f d/e/3.md } async fn write_file( - fs: &agentfs_sdk::filesystem::AgentFS, + fs: &agentfs_core::fs::AgentFS, path: &str, data: &[u8], uid: u32, diff --git a/cli/src/cmd/init.rs b/crates/agentfs-cli/src/cmd/init.rs similarity index 80% rename from cli/src/cmd/init.rs rename to crates/agentfs-cli/src/cmd/init.rs index 5adb7cd4..192c7728 100644 --- a/cli/src/cmd/init.rs +++ b/crates/agentfs-cli/src/cmd/init.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; -use agentfs_sdk::{ +use agentfs_core::{ agentfs_dir, AgentFS, AgentFSOptions, EncryptionConfig, OverlayFS, PartialBootstrapStrategy, PartialSyncOpts, SyncOptions, }; @@ -16,21 +16,34 @@ pub struct EncryptionOptions { pub cipher: String, } -pub async fn open_agentfs(options: AgentFSOptions) -> Result { +pub async fn open_agentfs(options: AgentFSOptions) -> Result { let mut options = options; + if options.core_config.is_none() { + options = options.with_core_config(crate::config::core_config_from_env()); + } // CLI handles env var fallback for auth token if options.sync.auth_token.is_none() { - if let Ok(auth_token) = std::env::var("TURSO_DB_AUTH_TOKEN") { - options.sync.auth_token = Some(auth_token); - } + options.sync.auth_token = crate::config::turso_db_auth_token(); } AgentFS::open(options).await } +/// Restore the single-file database family before a one-shot command exits: +/// reopening a WAL-mode database materializes a header-only `-wal` sidecar +/// even when the command never writes, which would undo the clean teardown +/// the owning session performed (invariant I1). Best-effort — a finalize +/// failure (e.g. a concurrent holder of the database) must not fail a +/// command whose real work already succeeded. +pub async fn finalize_readonly(agentfs: &AgentFS) { + if let Err(error) = agentfs.fs.finalize().await { + eprintln!("Warning: Failed to restore the single-file database family: {error:#}"); + } +} + pub fn build_sync_options(sync_cmd_options: &SyncCommandOptions) -> SyncOptions { let mut sync = SyncOptions { remote_url: sync_cmd_options.sync_remote_url.clone(), - auth_token: std::env::var("TURSO_DB_AUTH_TOKEN").ok(), + auth_token: crate::config::turso_db_auth_token(), partial_sync: None, }; @@ -131,6 +144,7 @@ pub async fn init_database( if let Some(base_path) = base.as_ref() { open_options = open_options.with_base(base_path); } + open_options = open_options.with_core_config(crate::config::core_config_from_env()); let encrypted = if let Some(enc_opts) = encryption { if sync_options.sync_remote_url.is_some() { @@ -193,6 +207,10 @@ pub async fn init_database( // If a command was provided, mount the filesystem and execute it if let Some(cmd_str) = command { run_init_cmd(&id, cmd_str, backend, base, agent).await?; + } else { + // The schema writes above land in a -wal; checkpoint it away so init + // exits with a single-file database family (invariant I1). + finalize_readonly(&agent).await; } Ok(()) @@ -206,9 +224,9 @@ async fn run_init_cmd( base: Option, agent: AgentFS, ) -> AnyhowResult<()> { - use crate::mount::{mount_fs, MountOpts}; - use agentfs_sdk::{FileSystem, HostFS}; - use std::process::Command; + use agentfs_core::{FileSystem, HostFS}; + use agentfs_mount::supervise::{exit_code_for_status, run_supervised}; + use agentfs_mount::{mount_fs, MountOpts}; use std::sync::Arc; let fs: Arc = if let Some(ref base_path) = base { @@ -228,7 +246,7 @@ async fn run_init_cmd( let mount_opts = MountOpts { mountpoint: mountpoint.clone(), - backend, + backend: backend.into(), fsname: format!("agentfs:{}", id), uid: None, gid: None, @@ -241,21 +259,21 @@ async fn run_init_cmd( let mount_handle = mount_fs(fs, mount_opts).await?; - let status = Command::new("sh") - .arg("-c") - .arg(&cmd_str) - .current_dir(&mountpoint) - .status() - .with_context(|| format!("Failed to execute: {}", cmd_str))?; - - drop(mount_handle); - - agentfs_sdk::profiling::report_summary("init_command_parent"); + let mut command = tokio::process::Command::new("sh"); + command.arg("-c").arg(&cmd_str).current_dir(&mountpoint); + // The CLI's private spill dir is process-internal; children keep the + // user's TMPDIR. + crate::config::restore_original_tmpdir(&mut command); + let status = run_supervised(mount_handle, command) + .await + .with_context(|| format!("Failed to execute: {}", cmd_str)); let _ = std::fs::remove_dir_all(&mountpoint); + let status = status?; if !status.success() { - std::process::exit(status.code().unwrap_or(1)); + crate::profiling::emit_cli_report(); + std::process::exit(exit_code_for_status(status)); } Ok(()) diff --git a/crates/agentfs-cli/src/cmd/mcp_server.rs b/crates/agentfs-cli/src/cmd/mcp_server.rs new file mode 100644 index 00000000..7f6b60f8 --- /dev/null +++ b/crates/agentfs-cli/src/cmd/mcp_server.rs @@ -0,0 +1,1175 @@ +use agentfs_core::fs::{FileSystem, DEFAULT_FILE_MODE}; +use agentfs_core::{AgentFS, AgentFSOptions, Stats}; +use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value as JsonValue}; +use std::collections::HashSet; +use std::io::{self, BufRead, Write}; +use std::path::Path; +use std::sync::Arc; +use tokio::sync::Mutex; + +use crate::cmd::init::{finalize_readonly, open_agentfs}; + +/// The complete dispatchable tool surface. `tools/list`, `tools/call` +/// dispatch, and `--tools` filter validation all key off this list; the +/// parity tests in `mcp_server/tests.rs` keep it honest. +const ALL_TOOLS: &[&str] = &[ + "read_file", + "write_file", + "readdir", + "mkdir", + "remove", + "rename", + "stat", + "access", + "kv_get", + "kv_set", + "kv_delete", + "kv_list", +]; + +const METHOD_NOT_FOUND: i64 = -32601; +const INVALID_PARAMS: i64 = -32602; +const INTERNAL_ERROR: i64 = -32603; + +/// A JSON-RPC error with the code it must be reported under. +#[derive(Debug)] +struct RpcError { + code: i64, + message: String, +} + +impl RpcError { + fn method_not_found(method: &str) -> Self { + Self { + code: METHOD_NOT_FOUND, + message: format!("Unknown method: {method}"), + } + } + + fn invalid_params(message: String) -> Self { + Self { + code: INVALID_PARAMS, + message, + } + } + + fn internal(message: String) -> Self { + Self { + code: INTERNAL_ERROR, + message, + } + } +} + +impl From for RpcError { + fn from(err: anyhow::Error) -> Self { + Self::internal(format!("{err:#}")) + } +} + +fn parse_tool_params(arguments: JsonValue) -> Result { + serde_json::from_value(arguments) + .map_err(|err| RpcError::invalid_params(format!("Invalid tool arguments: {err}"))) +} + +/// Main entry point for MCP server command +pub async fn handle_mcp_server_command( + id_or_path: String, + tools_filter: Option>, +) -> Result<()> { + // Resolve and open AgentFS + let options = AgentFSOptions::resolve(&id_or_path).context(format!( + "Failed to resolve agent ID or path: {}", + id_or_path + ))?; + + eprintln!("Using agent: {}", id_or_path); + + // Create MCP server with tool filtering + let server = McpServer::new(id_or_path, options, tools_filter).await?; + + // Run server with stdio transport + eprintln!("Starting MCP server on stdio..."); + eprintln!("Protocol: Model Context Protocol (MCP) over JSON-RPC 2.0"); + server.serve().await?; + + Ok(()) +} + +/// Where request handlers get their AgentFS from. +enum AgentFsSource { + /// Ephemeral (in-memory) databases live exactly as long as this handle. + Held(Arc), + /// File-backed databases are reopened per request. The database file + /// lock is exclusive (one process at a time), so holding it across the + /// whole stdio session would block every other CLI command (including + /// `agentfs timeline` reading the tool audit) for as long as a client + /// keeps the server open. Reopening per request releases the lock while + /// the server is idle. + PerRequest(Box), +} + +/// MCP Server implementation +struct McpServer { + /// The user-supplied database identity, kept for schema-mismatch + /// guidance on the per-request reopens (the database can be replaced + /// underneath a long-lived stdio session). + id_or_path: String, + source: AgentFsSource, + enabled_tools: Option>, +} + +impl McpServer { + async fn new( + id_or_path: String, + options: AgentFSOptions, + tools_filter: Option>, + ) -> Result { + let enabled_tools = match tools_filter { + None => { + eprintln!("No tool filter specified. Exposing all tools."); + None + } + Some(tools) => { + let mut unknown: Vec = tools + .iter() + .filter(|tool| !ALL_TOOLS.contains(&tool.as_str())) + .cloned() + .collect(); + unknown.sort(); + unknown.dedup(); + if !unknown.is_empty() { + anyhow::bail!( + "unknown tool(s) in --tools filter: {}. Available tools: {}", + unknown.join(", "), + ALL_TOOLS.join(", ") + ); + } + let set: HashSet = tools.into_iter().collect(); + if set.is_empty() { + anyhow::bail!( + "--tools filter selected no tools. Available tools: {}", + ALL_TOOLS.join(", ") + ); + } + eprintln!("Tool filter enabled. Exposing tools: {:?}", set); + Some(set) + } + }; + + let source = if options.db_path()? == ":memory:" { + let agentfs = open_agentfs(options) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; + AgentFsSource::Held(Arc::new(agentfs)) + } else { + // Open once up front so startup fails cleanly on a missing or + // incompatible database, then release the file lock with the + // single-file family restored: even this never-writing probe + // materializes a -wal sidecar (invariant I1). + let probe = open_agentfs(options.clone()) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; + finalize_readonly(&probe).await; + drop(probe); + AgentFsSource::PerRequest(Box::new(options)) + }; + + Ok(Self { + id_or_path, + source, + enabled_tools, + }) + } + + async fn agentfs(&self) -> Result> { + match &self.source { + AgentFsSource::Held(agentfs) => Ok(agentfs.clone()), + AgentFsSource::PerRequest(options) => { + let agentfs = open_agentfs((**options).clone()).await.map_err(|err| { + super::migrate::open_error_with_guidance(err, &self.id_or_path) + })?; + Ok(Arc::new(agentfs)) + } + } + } + + /// Check if a tool is enabled based on filter + fn is_tool_enabled(&self, tool_name: &str) -> bool { + match &self.enabled_tools { + None => true, // No filter = all enabled + Some(set) => set.contains(tool_name), + } + } + + async fn serve(self) -> Result<()> { + let server = Arc::new(Mutex::new(self)); + let result = Self::serve_stdio(&server).await; + // Per-request opens leave a -wal next to a file-backed database + // while serving; restore the single-file family at exit even when + // the stdio loop errored, mirroring the NFS server's + // finalize-on-shutdown (agentfs-nfs server/tcp.rs). + server.lock().await.finalize_on_shutdown().await; + result + } + + /// Restore the single-file database family before the server exits. + /// Best-effort like every one-shot command's finalize: a concurrent + /// holder of the database must not turn a clean shutdown into an error. + async fn finalize_on_shutdown(&self) { + // Ephemeral databases have no on-disk family to restore. + let AgentFsSource::PerRequest(options) = &self.source else { + return; + }; + match open_agentfs((**options).clone()) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &self.id_or_path)) + { + Ok(agentfs) => finalize_readonly(&agentfs).await, + Err(error) => eprintln!( + "Warning: Failed to reopen the database to restore the single-file family: {error:#}" + ), + } + } + + async fn serve_stdio(server: &Arc>) -> Result<()> { + let stdin = io::stdin(); + let mut stdout = io::stdout(); + + for line in stdin.lock().lines() { + let line = line?; + if line.trim().is_empty() { + continue; + } + + // Parse JSON-RPC request + let request: JsonValue = match serde_json::from_str(&line) { + Ok(req) => req, + Err(e) => { + eprintln!("Failed to parse JSON-RPC request: {}", e); + continue; + } + }; + + // Handle request + let response = server.lock().await.handle_request(request).await; + + // Write response to stdout + if let Some(resp) = response { + let resp_str = serde_json::to_string(&resp)?; + writeln!(stdout, "{}", resp_str)?; + stdout.flush()?; + } + } + + Ok(()) + } + + async fn handle_request(&self, request: JsonValue) -> Option { + // Extract request fields + let method = request.get("method")?.as_str()?; + let id = request.get("id").cloned(); + let params = request.get("params").cloned().unwrap_or(json!({})); + + eprintln!("Received request: method={}", method); + + // Handle method + let result = match method { + "initialize" => self.handle_initialize(params).await.map_err(RpcError::from), + // Spec notifications (e.g. notifications/initialized) are + // acknowledged by ignoring them; they must never get a response. + "initialized" => return None, + method if method.starts_with("notifications/") => return None, + "tools/list" => self.handle_tools_list().await.map_err(RpcError::from), + "tools/call" => self.handle_tools_call(params).await, + "resources/list" => self.handle_resources_list().await.map_err(RpcError::from), + "resources/read" => self + .handle_resources_read(params) + .await + .map_err(RpcError::from), + _ => Err(RpcError::method_not_found(method)), + }; + + // JSON-RPC requests without an id are notifications: process them + // above, but never respond (not even with an error). + let id = id.filter(|id| !id.is_null())?; + + // Build JSON-RPC response + let response = match result { + Ok(result) => { + json!({ + "jsonrpc": "2.0", + "id": id, + "result": result + }) + } + Err(e) => { + eprintln!("Error handling {}: {}", method, e.message); + json!({ + "jsonrpc": "2.0", + "id": id, + "error": { + "code": e.code, + "message": e.message + } + }) + } + }; + + Some(response) + } + + async fn handle_initialize(&self, _params: JsonValue) -> Result { + Ok(json!({ + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {}, + "resources": {} + }, + "serverInfo": { + "name": "agentfs-mcp-server", + "version": env!("CARGO_PKG_VERSION") + } + })) + } + + async fn handle_tools_list(&self) -> Result { + let tools = self.get_tool_definitions(); + Ok(json!({ "tools": tools })) + } + + async fn handle_tools_call(&self, params: JsonValue) -> Result { + let name = params + .get("name") + .and_then(|v| v.as_str()) + .ok_or_else(|| RpcError::invalid_params("Missing tool name".to_string()))?; + + let arguments = params.get("arguments").cloned().unwrap_or(json!({})); + + let agentfs = self + .agentfs() + .await + .map_err(|err| RpcError::internal(format!("Failed to open database: {err:#}")))?; + + // Every tools/call attempt is audited, including rejected ones, so + // `agentfs timeline` shows the full MCP activity for the database. + let audit_id = agentfs + .tools + .start(name, Some(arguments.clone())) + .await + .map_err(|err| { + RpcError::internal(format!("Failed to record tool call audit row: {err}")) + })?; + + let outcome = self.dispatch_tool(&agentfs, name, arguments).await; + + match &outcome { + Ok(text) => { + agentfs + .tools + .success(audit_id, Some(JsonValue::String(text.clone()))) + .await + } + Err(err) => agentfs.tools.error(audit_id, &err.message).await, + } + .map_err(|err| { + RpcError::internal(format!("Failed to record tool call audit row: {err}")) + })?; + + let result_text = outcome?; + + Ok(json!({ + "content": [ + { + "type": "text", + "text": result_text + } + ] + })) + } + + async fn dispatch_tool( + &self, + agentfs: &AgentFS, + name: &str, + arguments: JsonValue, + ) -> Result { + if !ALL_TOOLS.contains(&name) { + return Err(RpcError::invalid_params(format!("Unknown tool: {name}"))); + } + if !self.is_tool_enabled(name) { + return Err(RpcError::invalid_params(format!( + "Tool not enabled by --tools filter: {name}" + ))); + } + + let result = match name { + "read_file" => handle_read_file(agentfs, parse_tool_params(arguments)?).await, + "write_file" => handle_write_file(agentfs, parse_tool_params(arguments)?).await, + "readdir" => handle_readdir(agentfs, parse_tool_params(arguments)?).await, + "mkdir" => handle_mkdir(agentfs, parse_tool_params(arguments)?).await, + "rename" => handle_rename(agentfs, parse_tool_params(arguments)?).await, + "remove" => handle_remove(agentfs, parse_tool_params(arguments)?).await, + "stat" => handle_stat(agentfs, parse_tool_params(arguments)?).await, + "access" => handle_access(agentfs, parse_tool_params(arguments)?).await, + "kv_get" => handle_kv_get(agentfs, parse_tool_params(arguments)?).await, + "kv_set" => handle_kv_set(agentfs, parse_tool_params(arguments)?).await, + "kv_delete" => handle_kv_delete(agentfs, parse_tool_params(arguments)?).await, + "kv_list" => handle_kv_list(agentfs, parse_tool_params(arguments)?).await, + _ => Err(anyhow::anyhow!( + "Tool {name} is advertised but has no dispatch arm; \ + ALL_TOOLS and dispatch_tool are out of sync" + )), + }; + + result.map_err(RpcError::from) + } + + async fn handle_resources_list(&self) -> Result { + let agentfs = self.agentfs().await?; + let resources = list_resources(&agentfs).await?; + Ok(json!({ "resources": resources })) + } + + async fn handle_resources_read(&self, params: JsonValue) -> Result { + let uri = params + .get("uri") + .and_then(|v| v.as_str()) + .ok_or_else(|| anyhow::anyhow!("Missing resource uri"))?; + + let agentfs = self.agentfs().await?; + let contents = read_resource(&agentfs, uri).await?; + let mime_type = guess_mime_type(uri); + + // Try to decode as UTF-8, fall back to base64 + let resource_contents = if let Ok(text) = String::from_utf8(contents.clone()) { + json!({ + "uri": uri, + "mimeType": mime_type, + "text": text + }) + } else { + json!({ + "uri": uri, + "mimeType": mime_type, + "blob": base64_encode(&contents) + }) + }; + + Ok(json!({ "contents": [resource_contents] })) + } + + fn get_tool_definitions(&self) -> Vec { + let mut tools = Vec::new(); + + // Filesystem tools + if self.is_tool_enabled("read_file") { + tools.push(json!({ + "name": "read_file", + "description": "Read file contents from the filesystem", + "inputSchema": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to the file to read" + }, + "encoding": { + "type": "string", + "enum": ["utf8", "base64"], + "description": "Encoding to use for file contents (default: utf8)" + } + }, + "required": ["path"] + } + })); + } + + if self.is_tool_enabled("write_file") { + tools.push(json!({ + "name": "write_file", + "description": "Write content to a file in the filesystem. Existing files are overwritten in place and keep their mode; new files are created with mode 0644.", + "inputSchema": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to the file to write" + }, + "content": { + "type": "string", + "description": "Content to write to the file" + }, + "encoding": { + "type": "string", + "enum": ["utf8", "base64"], + "description": "Encoding of the content (default: utf8)" + }, + "create_dirs": { + "type": "boolean", + "description": "Create parent directories if they don't exist" + } + }, + "required": ["path", "content"] + } + })); + } + + if self.is_tool_enabled("readdir") { + tools.push(json!({ + "name": "readdir", + "description": "List contents of a directory", + "inputSchema": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to the directory to list" + } + }, + "required": ["path"] + } + })); + } + + if self.is_tool_enabled("mkdir") { + tools.push(json!({ + "name": "mkdir", + "description": "Create a directory", + "inputSchema": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path of the directory to create" + } + }, + "required": ["path"] + } + })); + } + + if self.is_tool_enabled("remove") { + tools.push(json!({ + "name": "remove", + "description": "Remove a file or empty directory", + "inputSchema": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path of the file or directory to remove" + } + }, + "required": ["path"] + } + })); + } + + if self.is_tool_enabled("rename") { + tools.push(json!({ + "name": "rename", + "description": "Move or rename a file or directory", + "inputSchema": { + "type": "object", + "properties": { + "from": { + "type": "string", + "description": "Source path" + }, + "to": { + "type": "string", + "description": "Destination path" + } + }, + "required": ["from", "to"] + } + })); + } + + if self.is_tool_enabled("stat") { + tools.push(json!({ + "name": "stat", + "description": "Get file or directory metadata", + "inputSchema": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to stat" + } + }, + "required": ["path"] + } + })); + } + + if self.is_tool_enabled("access") { + tools.push(json!({ + "name": "access", + "description": "Test if a path exists", + "inputSchema": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to test" + } + }, + "required": ["path"] + } + })); + } + + // KV store tools + if self.is_tool_enabled("kv_get") { + tools.push(json!({ + "name": "kv_get", + "description": "Get a value from the key-value store", + "inputSchema": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Key to retrieve" + } + }, + "required": ["key"] + } + })); + } + + if self.is_tool_enabled("kv_set") { + tools.push(json!({ + "name": "kv_set", + "description": "Set a value in the key-value store", + "inputSchema": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Key to set" + }, + "value": { + "description": "Value to store (any JSON value)" + } + }, + "required": ["key", "value"] + } + })); + } + + if self.is_tool_enabled("kv_delete") { + tools.push(json!({ + "name": "kv_delete", + "description": "Delete a key from the key-value store", + "inputSchema": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Key to delete" + } + }, + "required": ["key"] + } + })); + } + + if self.is_tool_enabled("kv_list") { + tools.push(json!({ + "name": "kv_list", + "description": "List keys in the key-value store with optional prefix", + "inputSchema": { + "type": "object", + "properties": { + "prefix": { + "type": "string", + "description": "Optional prefix to filter keys" + } + } + } + })); + } + + tools + } +} + +// ============================================================================ +// Helper functions +// ============================================================================ + +/// Normalize a path to ensure it starts with / +fn normalize_path(path: &str) -> Result { + let path = path.trim(); + + // Reject paths with .. for security + if path.contains("..") { + anyhow::bail!("Path traversal not allowed: {}", path); + } + + // Convert relative to absolute + let normalized = if path.starts_with('/') { + path.to_string() + } else if path.is_empty() { + "/".to_string() + } else { + format!("/{}", path) + }; + + Ok(normalized) +} + +/// Guess MIME type based on file extension +fn guess_mime_type(path: &str) -> String { + match Path::new(path).extension().and_then(|s| s.to_str()) { + Some("txt") | Some("md") => "text/plain", + Some("json") => "application/json", + Some("html") | Some("htm") => "text/html", + Some("js") | Some("mjs") => "application/javascript", + Some("ts") => "application/typescript", + Some("css") => "text/css", + Some("xml") => "application/xml", + Some("png") => "image/png", + Some("jpg") | Some("jpeg") => "image/jpeg", + Some("gif") => "image/gif", + Some("svg") => "image/svg+xml", + Some("pdf") => "application/pdf", + Some("zip") => "application/zip", + Some("tar") => "application/x-tar", + Some("gz") => "application/gzip", + _ => "application/octet-stream", + } + .to_string() +} + +/// Base64 encode bytes +fn base64_encode(data: &[u8]) -> String { + use base64::{engine::general_purpose::STANDARD, Engine}; + STANDARD.encode(data) +} + +/// Base64 decode string +fn base64_decode(s: &str) -> Result> { + use base64::{engine::general_purpose::STANDARD, Engine}; + STANDARD + .decode(s) + .map_err(|e| anyhow::anyhow!("Base64 decode error: {}", e)) +} + +// ============================================================================ +// Tool parameter types +// ============================================================================ + +#[derive(Debug, Serialize, Deserialize)] +struct ReadFileParams { + path: String, + #[serde(default)] + encoding: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct WriteFileParams { + path: String, + content: String, + #[serde(default)] + encoding: Option, + #[serde(default)] + create_dirs: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct ReaddirParams { + path: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct MkdirParams { + path: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct RenameParams { + from: String, + to: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct RemoveParams { + path: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct StatParams { + path: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct AccessParams { + path: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct KvGetParams { + key: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct KvSetParams { + key: String, + value: JsonValue, +} + +#[derive(Debug, Serialize, Deserialize)] +struct KvDeleteParams { + key: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct KvListParams { + #[serde(default)] + prefix: Option, +} + +// ============================================================================ +// Tool implementations +// ============================================================================ + +/// Read file contents +async fn handle_read_file(agentfs: &AgentFS, params: ReadFileParams) -> Result { + let path = normalize_path(¶ms.path)?; + + let data = agentfs + .fs + .read_file(&path) + .await + .context("Failed to read file")? + .ok_or_else(|| anyhow::anyhow!("File not found: {}", path))?; + + let content = match params.encoding.as_deref() { + Some("base64") => base64_encode(&data), + _ => String::from_utf8(data) + .context("File is not valid UTF-8. Use encoding=base64 for binary files.")?, + }; + + Ok(content) +} + +/// Write file contents. +/// +/// Existing files are overwritten in place so they keep their inode and +/// mode; new files are created with the default mode (0644). +async fn handle_write_file(agentfs: &AgentFS, params: WriteFileParams) -> Result { + let path = normalize_path(¶ms.path)?; + + let data = match params.encoding.as_deref() { + Some("base64") => base64_decode(¶ms.content)?, + _ => params.content.into_bytes(), + }; + + // Create parent directories if requested + if params.create_dirs.unwrap_or(false) { + ensure_parent_dirs(agentfs, &path).await?; + } + + let file = match agentfs.fs.stat(&path).await? { + Some(stats) if stats.is_file() => { + let file = FileSystem::open(&agentfs.fs, stats.ino, libc::O_WRONLY) + .await + .context("Failed to open file")?; + file.truncate(0).await.context("Failed to truncate file")?; + file + } + Some(_) => anyhow::bail!("Not a regular file: {}", path), + None => { + let (_, file) = agentfs + .fs + .create_file(&path, DEFAULT_FILE_MODE, 0, 0) + .await + .context("Failed to create file")?; + file + } + }; + file.pwrite(0, &data) + .await + .context("Failed to write file")?; + // Flush batched writes so the data is durable even if the server + // exits right after this call (each MCP call is a complete exchange). + file.drain_writes().await.context("Failed to flush file")?; + + Ok(format!("Wrote {} bytes to {}", data.len(), path)) +} + +/// Helper to create parent directories recursively +fn ensure_parent_dirs<'a>( + agentfs: &'a AgentFS, + path: &'a str, +) -> std::pin::Pin> + Send + 'a>> { + Box::pin(async move { + let path_obj = Path::new(path); + let parent = match path_obj.parent() { + Some(p) if !p.as_os_str().is_empty() && p != Path::new("/") => p, + _ => return Ok(()), + }; + + let parent_str = parent.to_string_lossy().to_string(); + let parent_path = normalize_path(&parent_str)?; + + // Check if parent exists + if agentfs.fs.stat(&parent_path).await?.is_some() { + return Ok(()); + } + + // Recursively ensure grandparent exists + ensure_parent_dirs(agentfs, &parent_path).await?; + + // Create parent + agentfs + .fs + .mkdir(&parent_path, 0, 0) + .await + .context(format!("Failed to create directory: {}", parent_path))?; + + Ok(()) + }) +} + +/// List directory contents +async fn handle_readdir(agentfs: &AgentFS, params: ReaddirParams) -> Result { + let path = normalize_path(¶ms.path)?; + + let stats = agentfs + .fs + .stat(&path) + .await + .context("Failed to stat directory")? + .ok_or_else(|| anyhow::anyhow!("Directory not found: {}", path))?; + + let entries = agentfs + .fs + .readdir(stats.ino) + .await + .context("Failed to read directory")? + .ok_or_else(|| anyhow::anyhow!("Directory not found: {}", path))?; + + Ok(serde_json::to_string_pretty(&entries)?) +} + +/// Create directory +async fn handle_mkdir(agentfs: &AgentFS, params: MkdirParams) -> Result { + let path = normalize_path(¶ms.path)?; + + agentfs + .fs + .mkdir(&path, 0, 0) + .await + .context("Failed to create directory")?; + + Ok(format!("Created directory: {}", path)) +} + +/// Remove empty directory +async fn handle_remove(agentfs: &AgentFS, params: RemoveParams) -> Result { + let path = normalize_path(¶ms.path)?; + + agentfs + .fs + .remove(&path) + .await + .context("Failed to remove directory")?; + + Ok(format!("Removed directory: {}", path)) +} + +/// Rename/move file or directory +async fn handle_rename(agentfs: &AgentFS, params: RenameParams) -> Result { + let from = normalize_path(¶ms.from)?; + let to = normalize_path(¶ms.to)?; + let (from_parent, from_name) = agentfs.fs.resolve_parent_and_name(&from).await?; + let (to_parent, to_name) = agentfs.fs.resolve_parent_and_name(&to).await?; + + FileSystem::rename(&agentfs.fs, from_parent, &from_name, to_parent, &to_name) + .await + .context("Failed to rename")?; + + Ok(format!("Renamed {} to {}", from, to)) +} + +/// Get file metadata +async fn handle_stat(agentfs: &AgentFS, params: StatParams) -> Result { + let path = normalize_path(¶ms.path)?; + + let stats = agentfs + .fs + .stat(&path) + .await + .context("Failed to stat")? + .ok_or_else(|| anyhow::anyhow!("Path not found: {}", path))?; + + Ok(serde_json::to_string_pretty(&StatsResponse::from(stats))?) +} + +/// Test if path exists +async fn handle_access(agentfs: &AgentFS, params: AccessParams) -> Result { + let path = normalize_path(¶ms.path)?; + + let exists = agentfs.fs.stat(&path).await?.is_some(); + + Ok(serde_json::to_string(&json!({ "exists": exists }))?) +} + +/// Get KV value +async fn handle_kv_get(agentfs: &AgentFS, params: KvGetParams) -> Result { + let value: Option = agentfs + .kv + .get(¶ms.key) + .await + .context("Failed to get value")?; + + Ok(serde_json::to_string_pretty(&value)?) +} + +/// Set KV value +async fn handle_kv_set(agentfs: &AgentFS, params: KvSetParams) -> Result { + agentfs + .kv + .set(¶ms.key, ¶ms.value) + .await + .context("Failed to set value")?; + + Ok(format!("Set key: {}", params.key)) +} + +/// Delete KV value +async fn handle_kv_delete(agentfs: &AgentFS, params: KvDeleteParams) -> Result { + agentfs + .kv + .delete(¶ms.key) + .await + .context("Failed to delete key")?; + + Ok(format!("Deleted key: {}", params.key)) +} + +/// List KV keys, optionally filtered by prefix +async fn handle_kv_list(agentfs: &AgentFS, params: KvListParams) -> Result { + let mut keys = agentfs.kv.keys().await.context("Failed to list keys")?; + if let Some(prefix) = ¶ms.prefix { + keys.retain(|key| key.starts_with(prefix.as_str())); + } + keys.sort(); + + Ok(serde_json::to_string_pretty(&keys)?) +} + +/// List all files as resources +async fn list_resources(agentfs: &AgentFS) -> Result> { + let mut resources = Vec::new(); + collect_file_resources(agentfs, "/", &mut resources).await?; + Ok(resources) +} + +/// Recursively collect file resources +fn collect_file_resources<'a>( + agentfs: &'a AgentFS, + path: &'a str, + resources: &'a mut Vec, +) -> std::pin::Pin> + Send + 'a>> { + Box::pin(async move { + let dir_stats = match agentfs.fs.stat(path).await? { + Some(s) => s, + None => return Ok(()), + }; + + let entries = match agentfs.fs.readdir(dir_stats.ino).await? { + Some(entries) => entries, + None => return Ok(()), + }; + + for entry in entries { + let full_path = if path == "/" { + format!("/{}", entry) + } else { + format!("{}/{}", path, entry) + }; + + let stats = match agentfs.fs.stat(&full_path).await? { + Some(s) => s, + None => continue, + }; + + if stats.is_file() { + resources.push(json!({ + "uri": full_path, + "name": entry, + "description": format!("File at {}", full_path), + "mimeType": guess_mime_type(&full_path) + })); + } else if stats.is_directory() { + // Recurse into subdirectory + collect_file_resources(agentfs, &full_path, resources).await?; + } + } + + Ok(()) + }) +} + +/// Read a resource by path +async fn read_resource(agentfs: &AgentFS, path: &str) -> Result> { + let normalized = normalize_path(path)?; + + let data = agentfs + .fs + .read_file(&normalized) + .await + .context("Failed to read file")? + .ok_or_else(|| anyhow::anyhow!("File not found: {}", normalized))?; + + Ok(data) +} + +// ============================================================================ +// Response types +// ============================================================================ + +#[derive(Debug, Serialize)] +struct StatsResponse { + ino: i64, + mode: u32, + nlink: u32, + uid: u32, + gid: u32, + size: i64, + atime: i64, + mtime: i64, + ctime: i64, + is_file: bool, + is_directory: bool, + is_symlink: bool, +} + +impl From for StatsResponse { + fn from(stats: Stats) -> Self { + Self { + ino: stats.ino, + mode: stats.mode, + nlink: stats.nlink, + uid: stats.uid, + gid: stats.gid, + size: stats.size, + atime: stats.atime, + mtime: stats.mtime, + ctime: stats.ctime, + is_file: stats.is_file(), + is_directory: stats.is_directory(), + is_symlink: stats.is_symlink(), + } + } +} + +#[cfg(test)] +mod tests; diff --git a/crates/agentfs-cli/src/cmd/mcp_server/tests.rs b/crates/agentfs-cli/src/cmd/mcp_server/tests.rs new file mode 100644 index 00000000..1c1974bb --- /dev/null +++ b/crates/agentfs-cli/src/cmd/mcp_server/tests.rs @@ -0,0 +1,449 @@ +use super::*; +use agentfs_core::fs::S_IFREG; +use agentfs_core::{error::Error as SdkError, FsError, ToolCallStatus}; + +const ROOT_INO: i64 = 1; + +/// Unit tests run against an ephemeral database (Held source) so state and +/// audit rows are shared across requests within one server. The PerRequest +/// reopen semantics are inherently cross-process and are covered by the +/// stdio shell suite (tests/test-mcp-server.sh). +async fn create_test_server() -> Result<(McpServer, Arc)> { + create_filtered_test_server(None).await +} + +async fn create_filtered_test_server( + tools_filter: Option>, +) -> Result<(McpServer, Arc)> { + let server = McpServer::new( + ":memory:".to_string(), + AgentFSOptions::ephemeral(), + tools_filter, + ) + .await?; + let agentfs = server.agentfs().await?; + Ok((server, agentfs)) +} + +async fn call_tool(server: &McpServer, id: i64, name: &str, arguments: JsonValue) -> JsonValue { + server + .handle_request(json!({ + "jsonrpc": "2.0", + "id": id, + "method": "tools/call", + "params": { "name": name, "arguments": arguments } + })) + .await + .expect("tools/call requests must produce a response") +} + +fn advertised_tool_names(server: &McpServer) -> Vec { + server + .get_tool_definitions() + .iter() + .map(|tool| { + tool.get("name") + .and_then(|name| name.as_str()) + .expect("every tool definition has a name") + .to_string() + }) + .collect() +} + +#[tokio::test] +async fn tools_list_equals_dispatch_surface() -> Result<()> { + let (server, agentfs) = create_test_server().await?; + + assert_eq!( + advertised_tool_names(&server), + ALL_TOOLS.to_vec(), + "tools/list must advertise exactly the canonical tool surface" + ); + + // Every advertised tool must dispatch with minimal valid arguments. + agentfs.fs.mkdir("/dir", 0, 0).await?; + let minimal_args = |name: &str| -> JsonValue { + match name { + "read_file" => json!({"path": "/seed.txt"}), + "write_file" => json!({"path": "/seed.txt", "content": "seed"}), + "readdir" => json!({"path": "/"}), + "mkdir" => json!({"path": "/made"}), + "remove" => json!({"path": "/made"}), + "rename" => json!({"from": "/seed.txt", "to": "/renamed.txt"}), + "stat" => json!({"path": "/renamed.txt"}), + "access" => json!({"path": "/renamed.txt"}), + "kv_get" => json!({"key": "k"}), + "kv_set" => json!({"key": "k", "value": {"nested": true}}), + "kv_delete" => json!({"key": "k"}), + "kv_list" => json!({}), + other => panic!("no minimal arguments defined for tool {other}"), + } + }; + // Dispatch order satisfies data dependencies (write before read/rename). + let order = [ + "write_file", + "read_file", + "readdir", + "mkdir", + "remove", + "rename", + "stat", + "access", + "kv_set", + "kv_get", + "kv_list", + "kv_delete", + ]; + assert_eq!( + { + let mut sorted = order.to_vec(); + sorted.sort_unstable(); + sorted + }, + { + let mut sorted = ALL_TOOLS.to_vec(); + sorted.sort_unstable(); + sorted + }, + "dispatch-order fixture must cover the canonical tool surface" + ); + + for (idx, name) in order.iter().enumerate() { + let response = call_tool(&server, idx as i64 + 1, name, minimal_args(name)).await; + assert!( + response.get("error").is_none(), + "advertised tool {name} failed to dispatch: {response}" + ); + } + Ok(()) +} + +#[tokio::test] +async fn kv_list_dispatches_and_honors_prefix() -> Result<()> { + let (_server, agentfs) = create_test_server().await?; + agentfs.kv.set("app/one", &json!(1)).await?; + agentfs.kv.set("app/two", &json!(2)).await?; + agentfs.kv.set("other", &json!(3)).await?; + + let all = handle_kv_list(&agentfs, KvListParams { prefix: None }).await?; + let all: Vec = serde_json::from_str(&all)?; + assert_eq!(all, vec!["app/one", "app/two", "other"]); + + let filtered = handle_kv_list( + &agentfs, + KvListParams { + prefix: Some("app/".to_string()), + }, + ) + .await?; + let filtered: Vec = serde_json::from_str(&filtered)?; + assert_eq!(filtered, vec!["app/one", "app/two"]); + Ok(()) +} + +#[tokio::test] +async fn tool_calls_are_audited_with_status_and_timing() -> Result<()> { + let (server, agentfs) = create_test_server().await?; + + let ok = call_tool( + &server, + 1, + "write_file", + json!({"path": "/audit.txt", "content": "hello"}), + ) + .await; + assert!(ok.get("error").is_none(), "write_file failed: {ok}"); + + let failed = call_tool(&server, 2, "read_file", json!({"path": "/missing.txt"})).await; + assert!( + failed.get("error").is_some(), + "read_file on a missing path must fail" + ); + + let calls = agentfs.tools.recent(None).await?; + assert_eq!(calls.len(), 2, "each tools/call must write one audit row"); + + // recent() orders by started_at, which can tie at millisecond + // resolution; look rows up by name instead. + let read_call = calls + .iter() + .find(|call| call.name == "read_file") + .expect("read_file audit row"); + assert_eq!(read_call.status, ToolCallStatus::Error); + assert!( + read_call + .error + .as_deref() + .unwrap_or("") + .contains("/missing.txt"), + "audit row should record the tool error, got {:?}", + read_call.error + ); + + let write_call = calls + .iter() + .find(|call| call.name == "write_file") + .expect("write_file audit row"); + assert_eq!(write_call.status, ToolCallStatus::Success); + assert_eq!( + write_call.parameters, + Some(json!({"path": "/audit.txt", "content": "hello"})), + "audit row should record the tool arguments" + ); + assert_eq!( + write_call.result, + Some(json!("Wrote 5 bytes to /audit.txt")), + "audit row should record the tool result" + ); + assert!(write_call.duration_ms.is_some()); + assert!(write_call.completed_at.is_some()); + Ok(()) +} + +#[tokio::test] +async fn write_file_preserves_existing_mode_and_creates_with_default() -> Result<()> { + let (server, agentfs) = create_test_server().await?; + + let (created, file) = agentfs + .fs + .create_file("/mode.txt", S_IFREG | 0o755, 0, 0) + .await?; + file.pwrite(0, b"original content").await?; + file.drain_writes().await?; + assert_eq!(created.mode, S_IFREG | 0o755); + + let response = call_tool( + &server, + 1, + "write_file", + json!({"path": "/mode.txt", "content": "overwritten"}), + ) + .await; + assert!(response.get("error").is_none(), "write failed: {response}"); + + let stats = agentfs.fs.stat("/mode.txt").await?.unwrap(); + assert_eq!( + stats.mode, + S_IFREG | 0o755, + "overwriting an existing file must not change its mode" + ); + assert_eq!(stats.size, "overwritten".len() as i64); + assert_eq!( + agentfs.fs.read_file("/mode.txt").await?.unwrap(), + b"overwritten".to_vec() + ); + + let response = call_tool( + &server, + 2, + "write_file", + json!({"path": "/fresh.txt", "content": "new"}), + ) + .await; + assert!(response.get("error").is_none(), "write failed: {response}"); + let stats = agentfs.fs.stat("/fresh.txt").await?.unwrap(); + assert_eq!( + stats.mode, DEFAULT_FILE_MODE, + "new files must be created with the documented default mode 0644" + ); + Ok(()) +} + +#[tokio::test] +async fn unknown_tool_is_rejected_with_invalid_params_and_audited() -> Result<()> { + let (server, agentfs) = create_test_server().await?; + + let response = call_tool(&server, 1, "copy_file", json!({})).await; + let error = response + .get("error") + .expect("unknown tool must produce a JSON-RPC error"); + assert_eq!( + error.get("code").and_then(|c| c.as_i64()), + Some(INVALID_PARAMS) + ); + assert!( + error + .get("message") + .and_then(|m| m.as_str()) + .unwrap_or("") + .contains("copy_file"), + "error should name the unknown tool: {error}" + ); + + let calls = agentfs.tools.recent(None).await?; + assert_eq!(calls.len(), 1, "rejected calls are audited too"); + assert_eq!(calls[0].name, "copy_file"); + assert_eq!(calls[0].status, ToolCallStatus::Error); + Ok(()) +} + +#[tokio::test] +async fn notifications_are_accepted_without_response() -> Result<()> { + let (server, _agentfs) = create_test_server().await?; + + // The standard post-initialize notification from strict clients. + assert_eq!( + server + .handle_request(json!({ + "jsonrpc": "2.0", + "method": "notifications/initialized" + })) + .await, + None, + "notifications/initialized must be accepted without a response" + ); + // Legacy spelling. + assert_eq!( + server + .handle_request(json!({"jsonrpc": "2.0", "method": "initialized"})) + .await, + None + ); + // Any request without an id is a notification and never gets a response, + // even when it would error. + assert_eq!( + server + .handle_request(json!({"jsonrpc": "2.0", "method": "bogus/method"})) + .await, + None + ); + Ok(()) +} + +#[tokio::test] +async fn unknown_method_with_id_returns_method_not_found() -> Result<()> { + let (server, _agentfs) = create_test_server().await?; + + let response = server + .handle_request(json!({"jsonrpc": "2.0", "id": 7, "method": "bogus/method"})) + .await + .expect("requests with an id must produce a response"); + let error = response.get("error").expect("unknown method must error"); + assert_eq!( + error.get("code").and_then(|c| c.as_i64()), + Some(METHOD_NOT_FOUND) + ); + assert_eq!(response.get("id").and_then(|id| id.as_i64()), Some(7)); + Ok(()) +} + +#[tokio::test] +async fn tools_filter_limits_both_listing_and_dispatch() -> Result<()> { + let (server, agentfs) = + create_filtered_test_server(Some(vec!["read_file".to_string(), "kv_list".to_string()])) + .await?; + + assert_eq!( + advertised_tool_names(&server), + vec!["read_file".to_string(), "kv_list".to_string()], + "tools/list must contain exactly the filtered tools" + ); + + let response = call_tool( + &server, + 1, + "write_file", + json!({"path": "/blocked.txt", "content": "nope"}), + ) + .await; + let error = response + .get("error") + .expect("disabled tools must be rejected at call time"); + assert_eq!( + error.get("code").and_then(|c| c.as_i64()), + Some(INVALID_PARAMS) + ); + assert!( + agentfs.fs.stat("/blocked.txt").await?.is_none(), + "disabled tool must not have side effects" + ); + + let calls = agentfs.tools.recent(None).await?; + assert_eq!(calls.len(), 1); + assert_eq!(calls[0].status, ToolCallStatus::Error); + Ok(()) +} + +#[tokio::test] +async fn unknown_tools_filter_is_rejected_at_startup() -> Result<()> { + let error = match McpServer::new( + ":memory:".to_string(), + AgentFSOptions::ephemeral(), + Some(vec!["copy_file".to_string()]), + ) + .await + { + Ok(_) => panic!("phantom tool names must be rejected at startup"), + Err(error) => error, + }; + let message = format!("{error:#}"); + assert!( + message.contains("copy_file") && message.contains("kv_list"), + "startup error should name the unknown tool and list the available ones: {message}" + ); + Ok(()) +} + +#[tokio::test] +async fn mcp_rename_directory_into_own_subtree_returns_error_and_preserves_namespace() -> Result<()> +{ + let (server, agentfs) = create_test_server().await?; + agentfs.fs.mkdir("/parent", 0, 0).await?; + agentfs.fs.mkdir("/parent/child", 0, 0).await?; + + let parent_ino = agentfs.fs.stat("/parent").await?.unwrap().ino; + let child_ino = agentfs.fs.stat("/parent/child").await?.unwrap().ino; + let root_before = agentfs.fs.readdir(ROOT_INO).await?.unwrap(); + let parent_before = agentfs.fs.readdir(parent_ino).await?.unwrap(); + let child_before = agentfs.fs.readdir(child_ino).await?.unwrap(); + + let direct_error = handle_rename( + &agentfs, + RenameParams { + from: "/parent".to_string(), + to: "/parent/child/parent".to_string(), + }, + ) + .await + .expect_err("cycle rename should fail"); + assert!( + direct_error.chain().any(|cause| { + matches!( + cause.downcast_ref::(), + Some(SdkError::Fs(FsError::InvalidRename)) + ) + }), + "expected InvalidRename in error chain, got {direct_error:#}" + ); + + let response = server + .handle_request(json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "rename", + "arguments": { + "from": "/parent", + "to": "/parent/child/parent" + } + } + })) + .await + .expect("tools/call requests must produce a response"); + assert!( + response.get("error").is_some(), + "cycle rename should return a JSON-RPC error response: {response}" + ); + + assert_eq!(agentfs.fs.readdir(ROOT_INO).await?.unwrap(), root_before); + assert_eq!( + agentfs.fs.readdir(parent_ino).await?.unwrap(), + parent_before + ); + assert_eq!(agentfs.fs.readdir(child_ino).await?.unwrap(), child_before); + assert!(agentfs.fs.stat("/parent").await?.is_some()); + assert!(agentfs.fs.stat("/parent/child").await?.is_some()); + assert!(agentfs.fs.stat("/parent/child/parent").await?.is_none()); + Ok(()) +} diff --git a/cli/src/cmd/migrate.rs b/crates/agentfs-cli/src/cmd/migrate.rs similarity index 60% rename from cli/src/cmd/migrate.rs rename to crates/agentfs-cli/src/cmd/migrate.rs index ae6c2c66..93bb8c3a 100644 --- a/cli/src/cmd/migrate.rs +++ b/crates/agentfs-cli/src/cmd/migrate.rs @@ -1,8 +1,16 @@ //! Database schema migration command. //! -//! Migrates an agentfs SQLite database to the current schema version. - -use agentfs_sdk::{AgentFSOptions, SchemaVersion}; +//! One `agentfs migrate` lands any supported old schema at the current +//! version: in place by default (every supported migration is an additive, +//! transactional ALTER), or copy-based via `--copy ` for users who +//! want a rebuilt database with the current chunk layout, keeping the +//! hash/verify engine from the historical `migrate-v0-5` command. + +use agentfs_core::{ + config::{DEFAULT_CHUNK_SIZE, DEFAULT_INLINE_THRESHOLD}, + error::Error as SdkError, + schema, AgentFSOptions, SchemaVersion, +}; use anyhow::{Context, Result as AnyhowResult}; use std::collections::{hash_map::DefaultHasher, HashSet}; use std::fs; @@ -10,19 +18,50 @@ use std::hash::{Hash, Hasher}; use std::io::{Read as IoRead, Write}; use std::path::{Path, PathBuf}; use turso::transaction::{Transaction, TransactionBehavior}; -use turso::{Builder, Connection, Value}; +use turso::{Connection, Value}; + +use super::safety::{build_local_database, ReadOnlyOpenSidecars}; -const V0_5_SCHEMA_VERSION: &str = "0.5"; -const V0_5_CHUNK_SIZE: usize = 65_536; -const V0_5_INLINE_THRESHOLD: usize = 4_096; const S_IFMT: i64 = 0o170000; const S_IFREG: i64 = 0o100000; -/// Handle the migrate command. +/// Guidance for a schema-version mismatch surfaced by an open path. +/// +/// Names a command that actually finishes the job: supported old versions get +/// `agentfs migrate ` (which lands at CURRENT in one invocation); +/// anything else is from a newer agentfs and no local command can help. +pub fn schema_upgrade_guidance(found: &str, expected: &str, id_or_path: &str) -> String { + match SchemaVersion::parse(found) { + Some(version) if version < schema::CURRENT => format!( + "Filesystem `{id_or_path}` requires migration\n\n\ + Found schema version {found}, but this version of agentfs requires {expected}.\n\n\ + To upgrade, run:\n\n agentfs migrate {id_or_path}\n" + ), + _ => format!( + "Filesystem `{id_or_path}` has unsupported schema version {found}; \ + this version of agentfs supports up to {expected}.\n\ + The database was likely created by a newer agentfs. Upgrade agentfs to open it." + ), + } +} + +/// Convert an SDK open error into an anyhow error, attaching migrate guidance +/// when the failure is a schema-version mismatch. +pub fn open_error_with_guidance(err: SdkError, id_or_path: &str) -> anyhow::Error { + match &err { + SdkError::SchemaVersionMismatch { found, expected } => { + anyhow::anyhow!("{}", schema_upgrade_guidance(found, expected, id_or_path)) + } + _ => err.into(), + } +} + +/// Handle the in-place migrate command. pub async fn handle_migrate_command( stdout: &mut impl Write, id_or_path: String, dry_run: bool, + encryption: Option<&(String, String)>, ) -> AnyhowResult<()> { let options = AgentFSOptions::resolve(&id_or_path)?; let db_path_str = options @@ -36,30 +75,47 @@ pub async fn handle_migrate_command( writeln!(stdout, "Database: {}", db_path.display())?; - // Open database directly using turso::Builder (not SDK) to avoid version check - let db = Builder::new_local(&db_path_str) - .build() - .await - .context("Failed to open database")?; + let sidecars = ReadOnlyOpenSidecars::capture(db_path); + let result = migrate_in_place(stdout, db_path, dry_run, encryption, &sidecars).await; + if result.is_err() { + // A failed migrate must not leave behind the frameless WAL/SHM its + // own open materialized (single-file invariant I1); the database and + // connection are already dropped when migrate_in_place returns. + sidecars.remove_created_frameless(); + } + result +} + +/// The open-to-finish body of the in-place migrate. Owns the database handle +/// so every return path (success or error) has dropped it before the caller +/// runs sidecar cleanup. +async fn migrate_in_place( + stdout: &mut impl Write, + db_path: &Path, + dry_run: bool, + encryption: Option<&(String, String)>, + sidecars: &ReadOnlyOpenSidecars, +) -> AnyhowResult<()> { + // Open directly via turso::Builder (not the SDK) so the open-path version + // gate does not reject the database migrate exists to upgrade. + let db = build_local_database(db_path, encryption).await?; let conn = db.connect().context("Failed to connect to database")?; - // Detect current schema version using SDK - let current_version = agentfs_sdk::schema::detect_schema_version(&conn) + let current_version = schema::detect_schema_version(&conn) .await? .unwrap_or(SchemaVersion::V0_0); writeln!(stdout, "Current schema version: {}", current_version)?; - writeln!(stdout, "Target schema version: 0.4 (legacy in-place)")?; - - if current_version == SchemaVersion::V0_5 { - writeln!(stdout, "Database is already at schema v0.5.")?; - return Ok(()); - } - - if current_version == SchemaVersion::V0_4 { - writeln!( - stdout, - "Database is at legacy schema v0.4. Use migrate-v0-5 for copy-based v0.5 migration." - )?; + writeln!( + stdout, + "Target schema version: {} (CURRENT)", + schema::CURRENT + )?; + + if current_version == schema::CURRENT { + writeln!(stdout, "Database is already at schema {}.", schema::CURRENT)?; + drop(conn); + drop(db); + sidecars.remove_created_frameless(); return Ok(()); } @@ -70,17 +126,21 @@ pub async fn handle_migrate_command( )?; print_pending_migrations(stdout, current_version)?; writeln!(stdout, "\nRun without --dry-run to apply migrations.")?; + drop(conn); + drop(db); + sidecars.remove_created_frameless(); } else { writeln!(stdout, "\nApplying migrations...")?; - apply_migrations(&conn, current_version, stdout).await?; + schema::ensure_current(&conn) + .await + .context("Failed to migrate schema to current")?; - // Store schema version in fs_config for future use - conn.execute( - "INSERT OR REPLACE INTO fs_config (key, value) VALUES ('schema_version', ?)", - ["0.4"], - ) - .await - .context("Failed to store schema version")?; + // Leave a single portable main-db file behind: checkpoint the WAL and + // drop the now-empty sidecars. + checkpoint_target(&conn, db_path).await?; + drop(conn); + drop(db); + super::safety::remove_sqlite_sidecars_after_checkpoint(db_path)?; writeln!(stdout, "\nMigration completed successfully.")?; } @@ -88,15 +148,29 @@ pub async fn handle_migrate_command( Ok(()) } -/// Handle the copy-based v0.4 -> v0.5 migration command. -pub async fn handle_migrate_v0_5_command( +/// Handle the copy-based migration mode (`migrate --copy `). +pub async fn handle_migrate_copy_command( stdout: &mut impl Write, - source: PathBuf, + id_or_path: String, target: PathBuf, verify: bool, overwrite_target: bool, + encryption: Option<&(String, String)>, ) -> AnyhowResult<()> { - migrate_v0_4_to_v0_5(stdout, &source, &target, verify, overwrite_target).await + let options = AgentFSOptions::resolve(&id_or_path)?; + let source_path_str = options + .db_path() + .context("Failed to resolve database path")?; + let source = PathBuf::from(source_path_str); + copy_migrate_to_current( + stdout, + &source, + &target, + verify, + overwrite_target, + encryption, + ) + .await } /// Print pending migrations without applying them. @@ -105,163 +179,27 @@ fn print_pending_migrations( from_version: SchemaVersion, ) -> AnyhowResult<()> { match from_version { - SchemaVersion::V0_0 => { - writeln!(stdout, " - v0.0 -> v0.2: Add nlink column to fs_inode")?; - writeln!(stdout, " - v0.2 -> v0.4: Add atime_nsec, mtime_nsec, ctime_nsec, rdev columns to fs_inode")?; - } - SchemaVersion::V0_2 => { - writeln!(stdout, " - v0.2 -> v0.4: Add atime_nsec, mtime_nsec, ctime_nsec, rdev columns to fs_inode")?; - } - SchemaVersion::V0_4 => { - // Already at latest - } - SchemaVersion::V0_5 => { - // v0.5 uses the copy-based migrate-v0-5 command. - } - } - Ok(()) -} - -/// Apply migrations from the current version to the target version. -async fn apply_migrations( - conn: &turso::Connection, - from_version: SchemaVersion, - stdout: &mut impl Write, -) -> AnyhowResult<()> { - match from_version { - SchemaVersion::V0_0 => { - // Migrate v0.0 -> v0.2 - migrate_v0_0_to_v0_2(conn, stdout).await?; - // Then v0.2 -> v0.4 - migrate_v0_2_to_v0_4(conn, stdout).await?; - } - SchemaVersion::V0_2 => { - // Migrate v0.2 -> v0.4 - migrate_v0_2_to_v0_4(conn, stdout).await?; - } - SchemaVersion::V0_4 => { - // Already at latest version - } - SchemaVersion::V0_5 => { - // v0.5 uses the copy-based migrate-v0-5 command. - } - } - Ok(()) -} - -/// Migrate from v0.0 to v0.2: Add nlink column to fs_inode. -async fn migrate_v0_0_to_v0_2( - conn: &turso::Connection, - stdout: &mut impl Write, -) -> AnyhowResult<()> { - writeln!(stdout, " Migrating v0.0 -> v0.2...")?; - - // Add nlink column (idempotent - ignore if exists) - let result = conn - .execute( - "ALTER TABLE fs_inode ADD COLUMN nlink INTEGER NOT NULL DEFAULT 0", - (), - ) - .await; - - match result { - Ok(_) => writeln!(stdout, " Added nlink column to fs_inode")?, - Err(e) => { - // Check if it's a "duplicate column" error (column already exists) - let err_msg = e.to_string(); - if err_msg.contains("duplicate column") { - writeln!(stdout, " nlink column already exists (skipping)")?; - } else { - return Err(e).context("Failed to add nlink column"); - } - } - } - - writeln!(stdout, " v0.0 -> v0.2 migration complete.")?; - Ok(()) -} - -/// Migrate from v0.2 to v0.4: Add nanosecond timestamp columns and rdev. -async fn migrate_v0_2_to_v0_4( - conn: &turso::Connection, - stdout: &mut impl Write, -) -> AnyhowResult<()> { - writeln!(stdout, " Migrating v0.2 -> v0.4...")?; - - // Add atime_nsec column (idempotent) - add_column_idempotent( - conn, - stdout, - "atime_nsec", - "ALTER TABLE fs_inode ADD COLUMN atime_nsec INTEGER NOT NULL DEFAULT 0", - ) - .await?; - - // Add mtime_nsec column (idempotent) - add_column_idempotent( - conn, - stdout, - "mtime_nsec", - "ALTER TABLE fs_inode ADD COLUMN mtime_nsec INTEGER NOT NULL DEFAULT 0", - ) - .await?; - - // Add ctime_nsec column (idempotent) - add_column_idempotent( - conn, - stdout, - "ctime_nsec", - "ALTER TABLE fs_inode ADD COLUMN ctime_nsec INTEGER NOT NULL DEFAULT 0", - ) - .await?; - - // Add rdev column (idempotent) - add_column_idempotent( - conn, - stdout, - "rdev", - "ALTER TABLE fs_inode ADD COLUMN rdev INTEGER NOT NULL DEFAULT 0", - ) - .await?; - - writeln!(stdout, " v0.2 -> v0.4 migration complete.")?; - Ok(()) -} - -/// Add a column idempotently (ignore duplicate column errors). -async fn add_column_idempotent( - conn: &turso::Connection, - stdout: &mut impl Write, - column_name: &str, - sql: &str, -) -> AnyhowResult<()> { - let result = conn.execute(sql, ()).await; - - match result { - Ok(_) => writeln!(stdout, " Added {} column to fs_inode", column_name)?, - Err(e) => { - let err_msg = e.to_string(); - if err_msg.contains("duplicate column") { + version if version == schema::CURRENT => {} + version => { + for migration in schema::pending_migrations(version) { writeln!( stdout, - " {} column already exists (skipping)", - column_name + " - {} -> {}: {}", + migration.from, migration.to, migration.description )?; - } else { - return Err(e).context(format!("Failed to add {} column", column_name)); } } } - Ok(()) } -async fn migrate_v0_4_to_v0_5( +async fn copy_migrate_to_current( stdout: &mut impl Write, source_path: &Path, target_path: &Path, verify: bool, overwrite_target: bool, + encryption: Option<&(String, String)>, ) -> AnyhowResult<()> { if !source_path.exists() { anyhow::bail!("Source database not found: {}", source_path.display()); @@ -282,11 +220,7 @@ async fn migrate_v0_4_to_v0_5( remove_db_family(target_path)?; } - let source_db_path = source_path - .to_str() - .context("Source database path is not valid UTF-8")?; - let source_db = Builder::new_local(source_db_path) - .build() + let source_db = build_local_database(source_path, encryption) .await .context("Failed to open source database")?; let source_conn = source_db @@ -300,22 +234,20 @@ async fn migrate_v0_4_to_v0_5( .with_context(|| format!("Failed to hash source {}", source_path.display()))?; run_integrity_check(&source_conn, "source").await?; - let source_version = agentfs_sdk::schema::detect_schema_version(&source_conn) + let source_version = schema::detect_schema_version(&source_conn) .await? .unwrap_or(SchemaVersion::V0_0); - if source_version != SchemaVersion::V0_4 { + if source_version == schema::CURRENT { anyhow::bail!( - "Expected source schema v0.4, found {}. Run the existing migrate command first.", - source_version + "Source database is already at schema {}. Run `agentfs migrate {}` for in-place \ + normalization or `agentfs backup` for a portable copy.", + schema::CURRENT, + source_path.display() ); } let source_chunk_size = read_config_usize(&source_conn, "chunk_size", 4096).await?; - let target_db_path = target_path - .to_str() - .context("Target database path is not valid UTF-8")?; - let target_db = Builder::new_local(target_db_path) - .build() + let target_db = build_local_database(target_path, encryption) .await .context("Failed to create target database")?; let target_conn = target_db @@ -325,15 +257,20 @@ async fn migrate_v0_4_to_v0_5( writeln!(stdout, "Source: {}", source_path.display())?; writeln!(stdout, "Target: {}", target_path.display())?; writeln!(stdout, "Source schema version: {source_version}")?; - writeln!(stdout, "Target schema version: {V0_5_SCHEMA_VERSION}")?; + writeln!( + stdout, + "Target schema version: {} (CURRENT)", + schema::CURRENT + )?; - create_v0_5_schema(&target_conn).await?; + create_current_schema(&target_conn).await?; let txn = Transaction::new_unchecked(&target_conn, TransactionBehavior::Immediate).await?; let copy_result: AnyhowResult<()> = async { copy_fs_config(&source_conn, &target_conn).await?; migrate_inodes_and_file_data(&source_conn, &target_conn, source_chunk_size).await?; copy_table_common_columns(&source_conn, &target_conn, "fs_dentry").await?; + backfill_target_nlink_if_missing(&source_conn, &target_conn).await?; copy_table_common_columns(&source_conn, &target_conn, "fs_symlink").await?; copy_optional_whiteouts(&source_conn, &target_conn).await?; copy_optional_table_common_columns(&source_conn, &target_conn, "fs_origin").await?; @@ -354,7 +291,8 @@ async fn migrate_v0_4_to_v0_5( if verify { verify_migration_equivalence(&source_conn, &target_conn).await?; - checkpoint_target_and_verify_copy(&source_conn, &target_conn, target_path).await?; + checkpoint_target_and_verify_copy(&source_conn, &target_conn, target_path, encryption) + .await?; } else { checkpoint_target(&target_conn, target_path).await?; } @@ -374,164 +312,56 @@ async fn migrate_v0_4_to_v0_5( Ok(()) } -async fn create_v0_5_schema(conn: &Connection) -> AnyhowResult<()> { - conn.execute( - "CREATE TABLE fs_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL - )", - (), - ) - .await?; - conn.execute( - "CREATE TABLE fs_inode ( - ino INTEGER PRIMARY KEY AUTOINCREMENT, - mode INTEGER NOT NULL, - nlink INTEGER NOT NULL DEFAULT 0, - uid INTEGER NOT NULL DEFAULT 0, - gid INTEGER NOT NULL DEFAULT 0, - size INTEGER NOT NULL DEFAULT 0, - atime INTEGER NOT NULL, - mtime INTEGER NOT NULL, - ctime INTEGER NOT NULL, - rdev INTEGER NOT NULL DEFAULT 0, - atime_nsec INTEGER NOT NULL DEFAULT 0, - mtime_nsec INTEGER NOT NULL DEFAULT 0, - ctime_nsec INTEGER NOT NULL DEFAULT 0, - data_inline BLOB, - storage_kind INTEGER NOT NULL DEFAULT 0 - )", - (), - ) - .await?; - conn.execute( - "CREATE TABLE fs_dentry ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - parent_ino INTEGER NOT NULL, - ino INTEGER NOT NULL, - UNIQUE(parent_ino, name) - )", - (), - ) - .await?; - conn.execute( - "CREATE INDEX idx_fs_dentry_parent ON fs_dentry(parent_ino, name)", - (), - ) - .await?; - conn.execute( - "CREATE TABLE fs_data ( - ino INTEGER NOT NULL, - chunk_index INTEGER NOT NULL, - data BLOB NOT NULL, - PRIMARY KEY (ino, chunk_index) - )", - (), - ) - .await?; - conn.execute( - "CREATE TABLE fs_symlink ( - ino INTEGER PRIMARY KEY, - target TEXT NOT NULL - )", - (), - ) - .await?; - conn.execute( - "CREATE TABLE fs_whiteout ( - path TEXT PRIMARY KEY, - parent_path TEXT NOT NULL, - created_at INTEGER NOT NULL - )", - (), - ) - .await?; - conn.execute( - "CREATE INDEX idx_fs_whiteout_parent ON fs_whiteout(parent_path)", - (), - ) - .await?; - conn.execute( - "CREATE TABLE fs_origin ( - delta_ino INTEGER PRIMARY KEY, - base_ino INTEGER NOT NULL - )", - (), - ) - .await?; - conn.execute( - "CREATE TABLE fs_overlay_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL - )", - (), - ) - .await?; - conn.execute( - "CREATE TABLE kv_store ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - created_at INTEGER DEFAULT (unixepoch()), - updated_at INTEGER DEFAULT (unixepoch()) - )", - (), - ) - .await?; - conn.execute( - "CREATE INDEX idx_kv_store_created_at ON kv_store(created_at)", - (), - ) - .await?; - conn.execute( - "CREATE TABLE tool_calls ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - parameters TEXT, - result TEXT, - error TEXT, - status TEXT NOT NULL DEFAULT 'pending', - started_at INTEGER NOT NULL, - completed_at INTEGER, - duration_ms INTEGER - )", - (), - ) - .await?; - conn.execute("CREATE INDEX idx_tool_calls_name ON tool_calls(name)", ()) - .await?; - conn.execute( - "CREATE INDEX idx_tool_calls_started_at ON tool_calls(started_at)", - (), - ) - .await?; - - conn.execute( - "INSERT INTO fs_config (key, value) VALUES ('schema_version', ?)", - (V0_5_SCHEMA_VERSION,), - ) - .await?; - conn.execute( - "INSERT INTO fs_config (key, value) VALUES ('chunk_size', ?)", - (V0_5_CHUNK_SIZE.to_string(),), - ) - .await?; - conn.execute( - "INSERT INTO fs_config (key, value) VALUES ('inline_threshold', ?)", - (V0_5_INLINE_THRESHOLD.to_string(),), - ) - .await?; +async fn create_current_schema(conn: &Connection) -> AnyhowResult<()> { + schema::ensure_current(conn) + .await + .map_err(anyhow::Error::from) +} +/// The v0.0 -> v0.2 nlink backfill rule, applied on the copy target when the +/// source predates the nlink column. Must stay in step with the in-place +/// migration in `agentfs_core::schema`. +const NLINK_BACKFILL_CASE: &str = "CASE + WHEN fs_inode.ino = 1 THEN 2 + WHEN (fs_inode.mode & 61440) = 16384 + THEN MAX(1, (SELECT COUNT(*) FROM fs_dentry d WHERE d.ino = fs_inode.ino)) + ELSE (SELECT COUNT(*) FROM fs_dentry d WHERE d.ino = fs_inode.ino) + END"; + +async fn backfill_target_nlink_if_missing( + source: &Connection, + target: &Connection, +) -> AnyhowResult<()> { + if source_has_column(source, "fs_inode", "nlink").await? { + return Ok(()); + } + target + .execute( + &format!("UPDATE fs_inode SET nlink = {NLINK_BACKFILL_CASE}"), + (), + ) + .await?; Ok(()) } +async fn source_has_column(conn: &Connection, table: &str, column: &str) -> AnyhowResult { + Ok(get_table_columns(conn, table) + .await? + .iter() + .any(|name| name == column)) +} + async fn copy_fs_config(source: &Connection, target: &Connection) -> AnyhowResult<()> { let mut rows = source .query( "SELECT key, value FROM fs_config - WHERE key NOT IN ('schema_version', 'chunk_size', 'inline_threshold') + WHERE key NOT IN (?, ?, ?) ORDER BY key", - (), + ( + schema::CONFIG_SCHEMA_VERSION_KEY, + schema::CONFIG_CHUNK_SIZE_KEY, + schema::CONFIG_INLINE_THRESHOLD_KEY, + ), ) .await?; @@ -548,20 +378,26 @@ async fn copy_fs_config(source: &Connection, target: &Connection) -> AnyhowResul target .execute( - "INSERT OR REPLACE INTO fs_config (key, value) VALUES ('schema_version', ?)", - (V0_5_SCHEMA_VERSION,), + "INSERT OR REPLACE INTO fs_config (key, value) VALUES (?, ?)", + (schema::CONFIG_SCHEMA_VERSION_KEY, schema::CURRENT.as_str()), ) .await?; target .execute( - "INSERT OR REPLACE INTO fs_config (key, value) VALUES ('chunk_size', ?)", - (V0_5_CHUNK_SIZE.to_string(),), + "INSERT OR REPLACE INTO fs_config (key, value) VALUES (?, ?)", + ( + schema::CONFIG_CHUNK_SIZE_KEY, + DEFAULT_CHUNK_SIZE.to_string(), + ), ) .await?; target .execute( - "INSERT OR REPLACE INTO fs_config (key, value) VALUES ('inline_threshold', ?)", - (V0_5_INLINE_THRESHOLD.to_string(),), + "INSERT OR REPLACE INTO fs_config (key, value) VALUES (?, ?)", + ( + schema::CONFIG_INLINE_THRESHOLD_KEY, + DEFAULT_INLINE_THRESHOLD.to_string(), + ), ) .await?; Ok(()) @@ -572,15 +408,28 @@ async fn migrate_inodes_and_file_data( target: &Connection, source_chunk_size: usize, ) -> AnyhowResult<()> { - let mut rows = source - .query( - "SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime, rdev, - atime_nsec, mtime_nsec, ctime_nsec - FROM fs_inode - ORDER BY ino", - (), - ) - .await?; + // Older schemas predate some inode columns (v0.0: nlink; v0.0/v0.2: + // rdev and the *_nsec columns); select zeros for the missing ones so one + // copy loop serves every supported source version. + let source_columns = get_table_columns(source, "fs_inode").await?; + let select_column = |name: &str| { + if source_columns.iter().any(|column| column == name) { + quote_identifier(name) + } else { + format!("0 AS {}", quote_identifier(name)) + } + }; + let select_sql = format!( + "SELECT ino, mode, {}, uid, gid, size, atime, mtime, ctime, {}, {}, {}, {} + FROM fs_inode + ORDER BY ino", + select_column("nlink"), + select_column("rdev"), + select_column("atime_nsec"), + select_column("mtime_nsec"), + select_column("ctime_nsec"), + ); + let mut rows = source.query(&select_sql, ()).await?; while let Some(row) = rows.next().await? { let ino = row_i64(&row, 0)?; @@ -598,18 +447,18 @@ async fn migrate_inodes_and_file_data( let ctime_nsec = row_i64(&row, 12)?; let is_regular = (mode & S_IFMT) == S_IFREG; - let (storage_kind, data_inline) = if is_regular && (size as usize) <= V0_5_INLINE_THRESHOLD - { - let (bytes, dense) = - read_source_file_bytes(source, ino, size as usize, source_chunk_size).await?; - if dense { - (1_i64, Value::Blob(bytes)) + let (storage_kind, data_inline) = + if is_regular && (size as usize) <= DEFAULT_INLINE_THRESHOLD { + let (bytes, dense) = + read_source_file_bytes(source, ino, size as usize, source_chunk_size).await?; + if dense { + (1_i64, Value::Blob(bytes)) + } else { + (0_i64, Value::Null) + } } else { (0_i64, Value::Null) - } - } else { - (0_i64, Value::Null) - }; + }; target .execute( @@ -682,7 +531,7 @@ async fn copy_source_file_chunks_to_target( let mut remaining = &chunk_data[..std::cmp::min(chunk_data.len(), size - source_offset)]; while !remaining.is_empty() { - let next_target_index = (source_offset / V0_5_CHUNK_SIZE) as i64; + let next_target_index = (source_offset / DEFAULT_CHUNK_SIZE) as i64; if target_chunk_index != Some(next_target_index) { flush_target_chunk( target, @@ -693,12 +542,12 @@ async fn copy_source_file_chunks_to_target( ) .await?; target_chunk_index = Some(next_target_index); - let chunk_start = next_target_index as usize * V0_5_CHUNK_SIZE; - let chunk_len = std::cmp::min(V0_5_CHUNK_SIZE, size - chunk_start); + let chunk_start = next_target_index as usize * DEFAULT_CHUNK_SIZE; + let chunk_len = std::cmp::min(DEFAULT_CHUNK_SIZE, size - chunk_start); target_chunk = vec![0; chunk_len]; } - let in_chunk_offset = source_offset % V0_5_CHUNK_SIZE; + let in_chunk_offset = source_offset % DEFAULT_CHUNK_SIZE; let copy_len = std::cmp::min(remaining.len(), target_chunk.len() - in_chunk_offset); target_chunk[in_chunk_offset..in_chunk_offset + copy_len] .copy_from_slice(&remaining[..copy_len]); @@ -892,27 +741,11 @@ async fn verify_migration_equivalence( run_integrity_check(target, "target").await?; verify_target_v0_5_invariants(target).await?; verify_target_v0_5_config(target).await?; - compare_table_rows( - source, - target, - "fs_inode", - &[ - "ino", - "mode", - "nlink", - "uid", - "gid", - "size", - "atime", - "mtime", - "ctime", - "rdev", - "atime_nsec", - "mtime_nsec", - "ctime_nsec", - ], - ) - .await?; + // Compare the inode columns both sides share; columns the source predates + // (nlink for v0.0, rdev/*_nsec for v0.0/v0.2) are zero-defaulted or + // backfilled on the target and verified against their rule below. + compare_common_table_rows(source, target, "fs_inode").await?; + verify_target_nlink_rule_if_source_missing(source, target).await?; compare_table_rows( source, target, @@ -936,10 +769,27 @@ async fn verify_migration_equivalence( Ok(()) } +async fn verify_target_nlink_rule_if_source_missing( + source: &Connection, + target: &Connection, +) -> AnyhowResult<()> { + if source_has_column(source, "fs_inode", "nlink").await? { + return Ok(()); + } + let sql = format!("SELECT ino FROM fs_inode WHERE nlink != {NLINK_BACKFILL_CASE} LIMIT 1"); + let mut rows = target.query(&sql, ()).await?; + if let Some(row) = rows.next().await? { + let ino = row_i64(&row, 0).unwrap_or_default(); + anyhow::bail!("Target nlink backfill violates the migration rule (ino {ino})"); + } + Ok(()) +} + async fn checkpoint_target_and_verify_copy( source: &Connection, target: &Connection, target_path: &Path, + encryption: Option<&(String, String)>, ) -> AnyhowResult<()> { checkpoint_target(target, target_path).await?; let snapshot_path = target_path.with_extension("snapshot-check.db"); @@ -951,11 +801,7 @@ async fn checkpoint_target_and_verify_copy( snapshot_path.display() ) })?; - let snapshot_db_path = snapshot_path - .to_str() - .context("Snapshot check database path is not valid UTF-8")?; - let snapshot_db = Builder::new_local(snapshot_db_path) - .build() + let snapshot_db = build_local_database(&snapshot_path, encryption) .await .context("Failed to open target main-db snapshot")?; let snapshot_conn = snapshot_db @@ -984,7 +830,7 @@ async fn compare_regular_file_contents( target: &Connection, ) -> AnyhowResult<()> { let source_chunk_size = read_config_usize(source, "chunk_size", 4096).await?; - let target_chunk_size = read_config_usize(target, "chunk_size", V0_5_CHUNK_SIZE).await?; + let target_chunk_size = read_config_usize(target, "chunk_size", DEFAULT_CHUNK_SIZE).await?; let mut rows = source .query("SELECT ino, mode, size FROM fs_inode ORDER BY ino", ()) .await?; @@ -1113,17 +959,21 @@ async fn read_target_file_bytes( } async fn verify_target_v0_5_config(conn: &Connection) -> AnyhowResult<()> { - let schema_version = read_config_string(conn, "schema_version").await?; - if schema_version.as_deref() != Some(V0_5_SCHEMA_VERSION) { - anyhow::bail!("Target schema_version is not {V0_5_SCHEMA_VERSION}"); + let schema_version = read_config_string(conn, schema::CONFIG_SCHEMA_VERSION_KEY).await?; + if schema_version.as_deref() != Some(schema::CURRENT.as_str()) { + anyhow::bail!( + "Target {} is not {}", + schema::CONFIG_SCHEMA_VERSION_KEY, + schema::CURRENT + ); } - let chunk_size = read_config_usize(conn, "chunk_size", 0).await?; - if chunk_size != V0_5_CHUNK_SIZE { - anyhow::bail!("Target chunk_size is not {V0_5_CHUNK_SIZE}"); + let chunk_size = read_config_usize(conn, schema::CONFIG_CHUNK_SIZE_KEY, 0).await?; + if chunk_size != DEFAULT_CHUNK_SIZE { + anyhow::bail!("Target chunk_size is not {DEFAULT_CHUNK_SIZE}"); } - let inline_threshold = read_config_usize(conn, "inline_threshold", 0).await?; - if inline_threshold != V0_5_INLINE_THRESHOLD { - anyhow::bail!("Target inline_threshold is not {V0_5_INLINE_THRESHOLD}"); + let inline_threshold = read_config_usize(conn, schema::CONFIG_INLINE_THRESHOLD_KEY, 0).await?; + if inline_threshold != DEFAULT_INLINE_THRESHOLD { + anyhow::bail!("Target inline_threshold is not {DEFAULT_INLINE_THRESHOLD}"); } Ok(()) } @@ -1453,6 +1303,7 @@ fn sidecar_path(path: &Path, suffix: &str) -> PathBuf { mod tests { use super::*; use tempfile::NamedTempFile; + use turso::Builder; async fn create_test_db_v0_0() -> (turso::Database, NamedTempFile) { let file = NamedTempFile::new().unwrap(); @@ -1571,7 +1422,7 @@ mod tests { async fn detect_schema_version_for_test( conn: &turso::Connection, ) -> AnyhowResult { - Ok(agentfs_sdk::schema::detect_schema_version(conn) + Ok(schema::detect_schema_version(conn) .await? .unwrap_or(SchemaVersion::V0_0)) } @@ -1604,7 +1455,7 @@ mod tests { } #[tokio::test] - async fn test_migrate_v0_0_to_v0_4() { + async fn test_migrate_v0_0_to_current() { let (db, _file) = create_test_db_v0_0().await; let conn = db.connect().unwrap(); @@ -1614,21 +1465,16 @@ mod tests { SchemaVersion::V0_0 ); - // Apply migrations - let mut stdout = Vec::new(); - apply_migrations(&conn, SchemaVersion::V0_0, &mut stdout) - .await - .unwrap(); + schema::ensure_current(&conn).await.unwrap(); - // Verify now at v0.4 assert_eq!( detect_schema_version_for_test(&conn).await.unwrap(), - SchemaVersion::V0_4 + schema::CURRENT ); } #[tokio::test] - async fn test_migrate_v0_2_to_v0_4() { + async fn test_migrate_v0_2_to_current() { let (db, _file) = create_test_db_v0_2().await; let conn = db.connect().unwrap(); @@ -1638,16 +1484,11 @@ mod tests { SchemaVersion::V0_2 ); - // Apply migrations - let mut stdout = Vec::new(); - apply_migrations(&conn, SchemaVersion::V0_2, &mut stdout) - .await - .unwrap(); + schema::ensure_current(&conn).await.unwrap(); - // Verify now at v0.4 assert_eq!( detect_schema_version_for_test(&conn).await.unwrap(), - SchemaVersion::V0_4 + schema::CURRENT ); } @@ -1656,19 +1497,12 @@ mod tests { let (db, _file) = create_test_db_v0_0().await; let conn = db.connect().unwrap(); - // Apply migrations twice - should not error - let mut stdout = Vec::new(); - apply_migrations(&conn, SchemaVersion::V0_0, &mut stdout) - .await - .unwrap(); - apply_migrations(&conn, SchemaVersion::V0_0, &mut stdout) - .await - .unwrap(); + schema::ensure_current(&conn).await.unwrap(); + schema::ensure_current(&conn).await.unwrap(); - // Should still be at v0.4 assert_eq!( detect_schema_version_for_test(&conn).await.unwrap(), - SchemaVersion::V0_4 + schema::CURRENT ); } @@ -1678,7 +1512,7 @@ mod tests { let source = temp_dir.path().join("source.db"); let target = temp_dir.path().join("target.db"); let small_content = b"inline payload".to_vec(); - let large_content = patterned_bytes(V0_5_CHUNK_SIZE + 123, 0x42); + let large_content = patterned_bytes(DEFAULT_CHUNK_SIZE + 123, 0x42); let sparse_tail = b"tail!".to_vec(); create_synthetic_v0_4_database(&source, &small_content, &large_content, &sparse_tail).await; @@ -1686,9 +1520,16 @@ mod tests { let source_bytes_before = fs::read(&source).unwrap(); let mut stdout = Vec::new(); - handle_migrate_v0_5_command(&mut stdout, source.clone(), target.clone(), true, false) - .await - .unwrap(); + handle_migrate_copy_command( + &mut stdout, + source.to_string_lossy().into_owned(), + target.clone(), + true, + false, + None, + ) + .await + .unwrap(); assert_eq!(hash_file(&source).unwrap(), source_hash_before); assert_eq!(fs::read(&source).unwrap(), source_bytes_before); @@ -1725,13 +1566,14 @@ mod tests { assert!(matches!(row.get_value(1).unwrap(), Value::Null)); assert_eq!(table_count_for_test(&conn, "fs_data", "ino = 4").await, 2); - let migrated_large = read_target_file_bytes(&conn, 4, large_content.len(), V0_5_CHUNK_SIZE) - .await - .unwrap(); + let migrated_large = + read_target_file_bytes(&conn, 4, large_content.len(), DEFAULT_CHUNK_SIZE) + .await + .unwrap(); assert_eq!(migrated_large, large_content); let sparse_size = 2 * 4096 + sparse_tail.len(); - let migrated_sparse = read_target_file_bytes(&conn, 5, sparse_size, V0_5_CHUNK_SIZE) + let migrated_sparse = read_target_file_bytes(&conn, 5, sparse_size, DEFAULT_CHUNK_SIZE) .await .unwrap(); let mut expected_sparse = vec![0; 2 * 4096]; @@ -1791,7 +1633,7 @@ mod tests { .await .unwrap(); let target_conn = target_db.connect().unwrap(); - create_v0_5_schema(&target_conn).await.unwrap(); + create_current_schema(&target_conn).await.unwrap(); copy_optional_whiteouts(&source_conn, &target_conn) .await .unwrap(); @@ -1811,32 +1653,132 @@ mod tests { .unwrap(); } - async fn create_synthetic_v0_4_database( - path: &Path, - small_content: &[u8], - large_content: &[u8], - sparse_tail: &[u8], - ) { - let db = Builder::new_local(path.to_str().unwrap()) + #[tokio::test] + async fn test_in_place_migrate_backfills_legacy_whiteout_parent_path() { + let temp_dir = tempfile::tempdir().unwrap(); + let db_path = temp_dir.path().join("legacy-whiteout.db"); + create_synthetic_v0_4_database(&db_path, b"small", b"larger than inline", b"tail").await; + + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await + .unwrap(); + let conn = db.connect().unwrap(); + rewrite_whiteouts_without_parent_path(&conn).await; + drop(conn); + drop(db); + + let mut stdout = Vec::new(); + handle_migrate_command( + &mut stdout, + db_path.to_string_lossy().into_owned(), + false, + None, + ) + .await + .unwrap(); + let stdout = String::from_utf8(stdout).unwrap(); + assert!( + stdout.contains("Migration completed successfully."), + "unexpected migrate output: {stdout}" + ); + + let db = Builder::new_local(db_path.to_str().unwrap()) .build() .await .unwrap(); let conn = db.connect().unwrap(); + let columns = get_table_columns(&conn, "fs_whiteout").await.unwrap(); + assert!( + columns.iter().any(|column| column == "parent_path"), + "cli migrate did not add fs_whiteout.parent_path; columns={columns:?}" + ); + let mut rows = conn + .query( + "SELECT parent_path, created_at FROM fs_whiteout WHERE path = '/dir/deleted'", + (), + ) + .await + .unwrap(); + let row = rows.next().await.unwrap().unwrap(); + let parent_path = row.get::(0).unwrap(); + let created_at = row_i64(&row, 1).unwrap(); + println!( + "cli migrate: fs_whiteout columns={columns:?}; /dir/deleted parent_path={parent_path}" + ); + assert_eq!(parent_path, "/dir"); + assert_eq!(created_at, 123); + let mut rows = conn.query("PRAGMA user_version", ()).await.unwrap(); + let row = rows.next().await.unwrap().unwrap(); + assert_eq!(row_i64(&row, 0).unwrap(), schema::CURRENT.user_version()); + } + + async fn rewrite_whiteouts_without_parent_path(conn: &Connection) { conn.execute( - "CREATE TABLE fs_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL + "CREATE TABLE fs_whiteout_legacy_rows ( + path TEXT PRIMARY KEY, + created_at INTEGER NOT NULL )", (), ) .await .unwrap(); conn.execute( - "INSERT INTO fs_config (key, value) VALUES - ('schema_version', '0.4'), - ('chunk_size', '4096'), - ('custom_metadata', 'preserve-me')", + "INSERT INTO fs_whiteout_legacy_rows (path, created_at) + SELECT path, created_at FROM fs_whiteout", + (), + ) + .await + .unwrap(); + conn.execute("DROP TABLE fs_whiteout", ()).await.unwrap(); + conn.execute( + "CREATE TABLE fs_whiteout ( + path TEXT PRIMARY KEY, + created_at INTEGER NOT NULL + )", + (), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO fs_whiteout (path, created_at) + SELECT path, created_at FROM fs_whiteout_legacy_rows", + (), + ) + .await + .unwrap(); + conn.execute("DROP TABLE fs_whiteout_legacy_rows", ()) + .await + .unwrap(); + } + + async fn create_synthetic_v0_4_database( + path: &Path, + small_content: &[u8], + large_content: &[u8], + sparse_tail: &[u8], + ) { + let db = Builder::new_local(path.to_str().unwrap()) + .build() + .await + .unwrap(); + let conn = db.connect().unwrap(); + + conn.execute( + "CREATE TABLE fs_config ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + )", + (), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO fs_config (key, value) VALUES + ('schema_version', '0.4'), + ('chunk_size', '4096'), + ('custom_metadata', 'preserve-me')", (), ) .await @@ -2077,6 +2019,586 @@ mod tests { .unwrap(); } + #[test] + fn schema_upgrade_guidance_names_a_command_that_finishes_the_job() { + for (found, id_or_path) in [ + ("0.0", "my-agent"), + ("0.2", "/tmp/old.db"), + ("0.4", "other-agent"), + ] { + let guidance = schema_upgrade_guidance(found, "0.5", id_or_path); + assert!( + guidance.contains(&format!("agentfs migrate {id_or_path}")), + "{found}: {guidance}" + ); + assert!(!guidance.contains("migrate-v0-5"), "{found}: {guidance}"); + } + + let future = schema_upgrade_guidance("user_version 7", "0.5", "my-agent"); + assert!( + !future.contains("agentfs migrate"), + "future versions must not promise migrate can fix them: {future}" + ); + assert!(future.contains("newer agentfs"), "{future}"); + } + + #[tokio::test] + async fn test_copy_migrate_v0_0_source_lands_current_with_nlink_backfill() { + let temp_dir = tempfile::tempdir().unwrap(); + let source = temp_dir.path().join("source-v00.db"); + let target = temp_dir.path().join("target.db"); + let small = b"tiny".to_vec(); + let large = patterned_bytes(DEFAULT_CHUNK_SIZE + 77, 0x21); + create_synthetic_legacy_database(&source, SchemaVersion::V0_0, &small, &large).await; + + let mut stdout = Vec::new(); + handle_migrate_copy_command( + &mut stdout, + source.to_string_lossy().into_owned(), + target.clone(), + true, + false, + None, + ) + .await + .unwrap(); + let stdout = String::from_utf8(stdout).unwrap(); + assert!(stdout.contains("Source schema version: 0.0"), "{stdout}"); + assert!(stdout.contains("Verification completed successfully.")); + + let db = Builder::new_local(target.to_str().unwrap()) + .build() + .await + .unwrap(); + let conn = db.connect().unwrap(); + let mut rows = conn.query("PRAGMA user_version", ()).await.unwrap(); + let row = rows.next().await.unwrap().unwrap(); + assert_eq!(row_i64(&row, 0).unwrap(), schema::CURRENT.user_version()); + + for (ino, expected_nlink) in [(1, 2), (2, 1), (3, 1), (4, 2)] { + let mut rows = conn + .query("SELECT nlink FROM fs_inode WHERE ino = ?", (ino,)) + .await + .unwrap(); + let row = rows.next().await.unwrap().unwrap(); + assert_eq!( + row_i64(&row, 0).unwrap(), + expected_nlink, + "nlink for ino {ino}" + ); + } + + let migrated_small = read_target_file_bytes(&conn, 3, small.len(), DEFAULT_CHUNK_SIZE) + .await + .unwrap(); + assert_eq!(migrated_small, small); + let migrated_large = read_target_file_bytes(&conn, 4, large.len(), DEFAULT_CHUNK_SIZE) + .await + .unwrap(); + assert_eq!(migrated_large, large); + } + + #[tokio::test] + async fn test_copy_migrate_v0_2_source_lands_current() { + let temp_dir = tempfile::tempdir().unwrap(); + let source = temp_dir.path().join("source-v02.db"); + let target = temp_dir.path().join("target.db"); + let small = b"inline me".to_vec(); + let large = patterned_bytes(2 * DEFAULT_CHUNK_SIZE + 5, 0x55); + create_synthetic_legacy_database(&source, SchemaVersion::V0_2, &small, &large).await; + + let mut stdout = Vec::new(); + handle_migrate_copy_command( + &mut stdout, + source.to_string_lossy().into_owned(), + target.clone(), + true, + false, + None, + ) + .await + .unwrap(); + let stdout = String::from_utf8(stdout).unwrap(); + assert!(stdout.contains("Source schema version: 0.2"), "{stdout}"); + + let db = Builder::new_local(target.to_str().unwrap()) + .build() + .await + .unwrap(); + let conn = db.connect().unwrap(); + let mut rows = conn.query("PRAGMA user_version", ()).await.unwrap(); + let row = rows.next().await.unwrap().unwrap(); + assert_eq!(row_i64(&row, 0).unwrap(), schema::CURRENT.user_version()); + let migrated_large = read_target_file_bytes(&conn, 4, large.len(), DEFAULT_CHUNK_SIZE) + .await + .unwrap(); + assert_eq!(migrated_large, large); + } + + #[tokio::test] + async fn test_copy_migrate_rejects_current_source() { + let temp_dir = tempfile::tempdir().unwrap(); + let source = temp_dir.path().join("current.db"); + let target = temp_dir.path().join("target.db"); + { + let db = Builder::new_local(source.to_str().unwrap()) + .build() + .await + .unwrap(); + let conn = db.connect().unwrap(); + schema::ensure_current(&conn).await.unwrap(); + } + + let mut stdout = Vec::new(); + let err = handle_migrate_copy_command( + &mut stdout, + source.to_string_lossy().into_owned(), + target.clone(), + false, + false, + None, + ) + .await + .unwrap_err(); + assert!(err.to_string().contains("already at schema"), "{err}"); + } + + #[tokio::test] + async fn test_in_place_migrate_read_path_leaves_single_file_family() { + let temp_dir = tempfile::tempdir().unwrap(); + let db_path = temp_dir.path().join("current.db"); + { + let agent = agentfs_core::AgentFS::open(agentfs_core::AgentFSOptions::with_path( + db_path.to_string_lossy(), + )) + .await + .unwrap(); + agent.fs.finalize().await.unwrap(); + } + let wal = PathBuf::from(format!("{}-wal", db_path.display())); + let shm = PathBuf::from(format!("{}-shm", db_path.display())); + assert!(!wal.exists(), "fixture must start as a single file"); + + let mut stdout = Vec::new(); + handle_migrate_command( + &mut stdout, + db_path.to_string_lossy().into_owned(), + false, + None, + ) + .await + .unwrap(); + let stdout = String::from_utf8(stdout).unwrap(); + assert!(stdout.contains("already at schema"), "{stdout}"); + assert!( + !wal.exists(), + "already-current migrate must not leave a WAL sidecar it created" + ); + assert!( + !shm.exists(), + "already-current migrate must not leave an SHM sidecar it created" + ); + } + + #[tokio::test] + async fn test_failing_migrate_removes_frameless_sidecars_it_created() { + let temp_dir = tempfile::tempdir().unwrap(); + let db_path = temp_dir.path().join("future.db"); + { + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await + .unwrap(); + let conn = db.connect().unwrap(); + // A user_version no SchemaVersion maps to makes detect_schema_version + // (and thus the migrate) fail after the open already succeeded. + conn.execute("PRAGMA user_version = 999", ()).await.unwrap(); + let mut rows = conn + .query("PRAGMA wal_checkpoint(TRUNCATE)", ()) + .await + .unwrap(); + while rows.next().await.unwrap().is_some() {} + } + let wal = PathBuf::from(format!("{}-wal", db_path.display())); + let shm = PathBuf::from(format!("{}-shm", db_path.display())); + let _ = std::fs::remove_file(&wal); + let _ = std::fs::remove_file(&shm); + assert!(!wal.exists(), "fixture must start as a single file"); + + let mut stdout = Vec::new(); + let err = handle_migrate_command( + &mut stdout, + db_path.to_string_lossy().into_owned(), + false, + None, + ) + .await + .unwrap_err(); + assert!( + err.to_string().contains("user_version 999"), + "migrate must fail on the unsupported version, got: {err:#}" + ); + assert!( + !wal.exists(), + "failing migrate must not leave the frameless WAL sidecar its own open created" + ); + assert!( + !shm.exists(), + "failing migrate must not leave the SHM sidecar its own open created" + ); + } + + #[tokio::test] + async fn test_in_place_migrate_encrypted_v0_4_with_key() { + let key = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"; + let cipher = "aes256gcm"; + let temp_dir = tempfile::tempdir().unwrap(); + let db_path = temp_dir.path().join("encrypted-v04.db"); + + { + let db = Builder::new_local(db_path.to_str().unwrap()) + .experimental_encryption(true) + .with_encryption(turso::EncryptionOpts { + cipher: cipher.to_string(), + hexkey: key.to_string(), + }) + .build() + .await + .unwrap(); + let conn = db.connect().unwrap(); + create_synthetic_v0_4_fixture_tables(&conn).await; + } + + let encryption = (key.to_string(), cipher.to_string()); + let mut stdout = Vec::new(); + handle_migrate_command( + &mut stdout, + db_path.to_string_lossy().into_owned(), + false, + Some(&encryption), + ) + .await + .unwrap(); + let stdout = String::from_utf8(stdout).unwrap(); + assert!(stdout.contains("Current schema version: 0.4"), "{stdout}"); + assert!( + stdout.contains("Migration completed successfully."), + "{stdout}" + ); + + // No plaintext copy may appear next to the fixture during migration. + let names = fs::read_dir(temp_dir.path()) + .unwrap() + .map(|entry| entry.unwrap().file_name().to_string_lossy().into_owned()) + .filter(|name| !name.starts_with("encrypted-v04.db")) + .collect::>(); + assert!(names.is_empty(), "unexpected files: {names:?}"); + + let db = build_local_database(&db_path, Some(&encryption)) + .await + .unwrap(); + let conn = db.connect().unwrap(); + let mut rows = conn.query("PRAGMA user_version", ()).await.unwrap(); + let row = rows.next().await.unwrap().unwrap(); + assert_eq!(row_i64(&row, 0).unwrap(), schema::CURRENT.user_version()); + assert_eq!( + table_count_for_test(&conn, "fs_data", "ino = 2").await, + 1, + "file data must survive the encrypted migration" + ); + drop(conn); + drop(db); + + let without_key = async { + let db = build_local_database(&db_path, None).await?; + let conn = db.connect()?; + let mut rows = conn.query("SELECT COUNT(*) FROM fs_inode", ()).await?; + rows.next().await?; + Ok::<_, anyhow::Error>(()) + } + .await; + assert!( + without_key.is_err(), + "encrypted database must not open without the key" + ); + } + + async fn create_synthetic_v0_4_fixture_tables(conn: &Connection) { + conn.execute( + "CREATE TABLE fs_config (key TEXT PRIMARY KEY, value TEXT NOT NULL)", + (), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO fs_config (key, value) VALUES + ('schema_version', '0.4'), + ('chunk_size', '4096')", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_inode ( + ino INTEGER PRIMARY KEY AUTOINCREMENT, + mode INTEGER NOT NULL, + nlink INTEGER NOT NULL DEFAULT 0, + uid INTEGER NOT NULL DEFAULT 0, + gid INTEGER NOT NULL DEFAULT 0, + size INTEGER NOT NULL DEFAULT 0, + atime INTEGER NOT NULL, + mtime INTEGER NOT NULL, + ctime INTEGER NOT NULL, + rdev INTEGER NOT NULL DEFAULT 0, + atime_nsec INTEGER NOT NULL DEFAULT 0, + mtime_nsec INTEGER NOT NULL DEFAULT 0, + ctime_nsec INTEGER NOT NULL DEFAULT 0 + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_dentry ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parent_ino INTEGER NOT NULL, + ino INTEGER NOT NULL, + UNIQUE(parent_ino, name) + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_data ( + ino INTEGER NOT NULL, + chunk_index INTEGER NOT NULL, + data BLOB NOT NULL, + PRIMARY KEY (ino, chunk_index) + )", + (), + ) + .await + .unwrap(); + let dir_mode = 0o040000 | 0o755; + let file_mode = 0o100000 | 0o644; + conn.execute( + "INSERT INTO fs_inode + (ino, mode, nlink, uid, gid, size, atime, mtime, ctime, rdev, + atime_nsec, mtime_nsec, ctime_nsec) + VALUES + (1, ?, 2, 1000, 1000, 0, 10, 10, 10, 0, 0, 0, 0), + (2, ?, 1, 1000, 1000, 6, 11, 11, 11, 0, 0, 0, 0)", + (dir_mode, file_mode), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO fs_dentry (id, name, parent_ino, ino) VALUES (1, 'file.txt', 1, 2)", + (), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO fs_data (ino, chunk_index, data) VALUES (2, 0, ?)", + (Value::Blob(b"secret".to_vec()),), + ) + .await + .unwrap(); + } + + async fn create_synthetic_legacy_database( + path: &Path, + version: SchemaVersion, + small_content: &[u8], + large_content: &[u8], + ) { + assert!(matches!(version, SchemaVersion::V0_0 | SchemaVersion::V0_2)); + let db = Builder::new_local(path.to_str().unwrap()) + .build() + .await + .unwrap(); + let conn = db.connect().unwrap(); + + conn.execute( + "CREATE TABLE fs_config (key TEXT PRIMARY KEY, value TEXT NOT NULL)", + (), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO fs_config (key, value) VALUES + ('schema_version', ?), + ('chunk_size', '4096'), + ('custom_metadata', 'preserve-me')", + (version.as_str(),), + ) + .await + .unwrap(); + + let nlink_column = if version >= SchemaVersion::V0_2 { + "nlink INTEGER NOT NULL DEFAULT 0," + } else { + "" + }; + conn.execute( + &format!( + "CREATE TABLE fs_inode ( + ino INTEGER PRIMARY KEY AUTOINCREMENT, + mode INTEGER NOT NULL, + {nlink_column} + uid INTEGER NOT NULL DEFAULT 0, + gid INTEGER NOT NULL DEFAULT 0, + size INTEGER NOT NULL DEFAULT 0, + atime INTEGER NOT NULL, + mtime INTEGER NOT NULL, + ctime INTEGER NOT NULL + )" + ), + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_dentry ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parent_ino INTEGER NOT NULL, + ino INTEGER NOT NULL, + UNIQUE(parent_ino, name) + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_data ( + ino INTEGER NOT NULL, + chunk_index INTEGER NOT NULL, + data BLOB NOT NULL, + PRIMARY KEY (ino, chunk_index) + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_symlink (ino INTEGER PRIMARY KEY, target TEXT NOT NULL)", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE kv_store ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + created_at INTEGER DEFAULT (unixepoch()), + updated_at INTEGER DEFAULT (unixepoch()) + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE tool_calls ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parameters TEXT, + result TEXT, + error TEXT, + status TEXT NOT NULL DEFAULT 'pending', + started_at INTEGER NOT NULL, + completed_at INTEGER, + duration_ms INTEGER + )", + (), + ) + .await + .unwrap(); + + let dir_mode = 0o040000 | 0o755; + let file_mode = 0o100000 | 0o644; + let (columns, nlink_values): (&str, Vec) = if version >= SchemaVersion::V0_2 { + ( + "ino, mode, nlink, uid, gid, size, atime, mtime, ctime", + vec![2, 1, 1, 2], + ) + } else { + ("ino, mode, uid, gid, size, atime, mtime, ctime", Vec::new()) + }; + for (index, (ino, mode, size)) in [ + (1_i64, dir_mode, 0_i64), + (2, dir_mode, 0), + (3, file_mode, small_content.len() as i64), + (4, file_mode, large_content.len() as i64), + ] + .into_iter() + .enumerate() + { + let mut values = vec![Value::Integer(ino), Value::Integer(mode)]; + if version >= SchemaVersion::V0_2 { + values.push(Value::Integer(nlink_values[index])); + } + values.extend([ + Value::Integer(1000), + Value::Integer(1000), + Value::Integer(size), + Value::Integer(10), + Value::Integer(10), + Value::Integer(10), + ]); + let placeholders = std::iter::repeat_n("?", values.len()) + .collect::>() + .join(", "); + conn.execute( + &format!("INSERT INTO fs_inode ({columns}) VALUES ({placeholders})"), + values, + ) + .await + .unwrap(); + } + conn.execute( + "INSERT INTO fs_dentry (id, name, parent_ino, ino) VALUES + (1, 'dir', 1, 2), + (2, 'small.txt', 2, 3), + (3, 'large.bin', 2, 4), + (4, 'large-hardlink.bin', 2, 4)", + (), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO fs_data (ino, chunk_index, data) VALUES (3, 0, ?)", + (Value::Blob(small_content.to_vec()),), + ) + .await + .unwrap(); + for (chunk_index, chunk) in large_content.chunks(4096).enumerate() { + conn.execute( + "INSERT INTO fs_data (ino, chunk_index, data) VALUES (4, ?, ?)", + (chunk_index as i64, Value::Blob(chunk.to_vec())), + ) + .await + .unwrap(); + } + conn.execute( + "INSERT INTO kv_store (key, value, created_at, updated_at) + VALUES ('metadata', '{\"ok\":true}', 20, 21)", + (), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO tool_calls + (id, name, parameters, result, error, status, started_at, completed_at, duration_ms) + VALUES (1, 'legacy-test', '{}', '{}', '', 'success', 30, 31, 42)", + (), + ) + .await + .unwrap(); + } + async fn table_count_for_test(conn: &Connection, table: &str, where_clause: &str) -> i64 { let sql = format!("SELECT COUNT(*) FROM {table} WHERE {where_clause}"); let mut rows = conn.query(&sql, ()).await.unwrap(); diff --git a/cli/src/cmd/mod.rs b/crates/agentfs-cli/src/cmd/mod.rs similarity index 77% rename from cli/src/cmd/mod.rs rename to crates/agentfs-cli/src/cmd/mod.rs index e9da5af0..dfdb41f9 100644 --- a/cli/src/cmd/mod.rs +++ b/crates/agentfs-cli/src/cmd/mod.rs @@ -3,6 +3,7 @@ pub mod fs; pub mod init; pub mod mcp_server; pub mod migrate; +pub mod profiling; pub mod ps; pub mod safety; pub mod sync; @@ -10,9 +11,6 @@ pub mod timeline; #[cfg(unix)] pub mod mount; -#[cfg(not(unix))] -#[path = "mount_stub.rs"] -pub mod mount; mod run; @@ -28,5 +26,8 @@ pub mod exec; #[cfg(unix)] pub mod clone; -pub use mount::{mount, MountArgs, MountBackend}; +#[cfg(unix)] +pub use crate::opts::MountBackend; +#[cfg(unix)] +pub use mount::{mount, MountArgs}; pub use run::handle_run_command; diff --git a/cli/src/cmd/mount.rs b/crates/agentfs-cli/src/cmd/mount.rs similarity index 54% rename from cli/src/cmd/mount.rs rename to crates/agentfs-cli/src/cmd/mount.rs index a485e2a0..b16e1e38 100644 --- a/cli/src/cmd/mount.rs +++ b/crates/agentfs-cli/src/cmd/mount.rs @@ -1,37 +1,24 @@ -use agentfs_sdk::{ - error::Error as SdkError, AgentFSOptions, FileSystem, HostFS, OverlayFS, PartialOriginPolicy, -}; -use anyhow::{Context, Result}; -use std::{ - path::{Path, PathBuf}, - process::Command, - sync::Arc, +use agentfs_core::{ + AgentFSOptions, EncryptionConfig, FileSystem, HostFS, OverlayFS, PartialOriginPolicy, }; +use agentfs_mount::{mount_fs, Backend, MountOpts}; +use anyhow::Result; +use std::{path::PathBuf, sync::Arc}; use turso::value::Value; #[cfg(target_os = "linux")] -use crate::mount::unmount; -use crate::mount::{mount_fs, MountOpts}; -use crate::nfs::AgentNFS; -use crate::nfsserve::tcp::NFSTcp; - -#[cfg(target_os = "linux")] -use agentfs_sdk::{get_mounts, Mount}; +use agentfs_core::{error::Error as SdkError, get_mounts, Mount}; #[cfg(target_os = "linux")] use std::{ io::{self, Write}, os::unix::fs::MetadataExt, + path::Path, }; #[cfg(target_os = "linux")] use crate::cmd::init::open_agentfs; -#[cfg(target_os = "linux")] -use crate::fuse::FuseMountOptions; -pub use crate::opts::MountBackend; - -/// Default NFS port to try (use a high port to avoid needing root) -const DEFAULT_NFS_PORT: u32 = 11111; +use crate::opts::MountBackend; /// Arguments for the mount command. #[derive(Debug, Clone)] @@ -56,6 +43,21 @@ pub struct MountArgs { pub backend: MountBackend, /// Partial-origin policy for overlay copy-up. pub partial_origin_policy: Option, + /// Encryption key and cipher for encrypted databases. + pub encryption: Option<(String, String)>, +} + +fn apply_encryption( + options: AgentFSOptions, + encryption: &Option<(String, String)>, +) -> AgentFSOptions { + match encryption { + Some((key, cipher)) => options.with_encryption(EncryptionConfig { + hex_key: key.clone(), + cipher: cipher.clone(), + }), + None => options, + } } /// Mount the agent filesystem (Linux). @@ -63,10 +65,7 @@ pub struct MountArgs { pub fn mount(args: MountArgs) -> Result<()> { match args.backend { MountBackend::Fuse => mount_fuse(args), - MountBackend::Nfs => { - let rt = crate::get_runtime(); - rt.block_on(mount_nfs_backend(args)) - } + MountBackend::Nfs => mount_nfs(args), } } @@ -80,31 +79,61 @@ pub fn mount(args: MountArgs) -> Result<()> { Use --backend nfs (default) or `agentfs nfs` instead." ); } - MountBackend::Nfs => { - let rt = crate::get_runtime(); - rt.block_on(mount_nfs_backend(args)) - } + MountBackend::Nfs => mount_nfs(args), + } +} + +/// Mount the agent filesystem via the NFS backend. +/// +/// Deliberately synchronous: the non-foreground arm must fork before any +/// tokio runtime exists (forking inside a live multi-threaded runtime can +/// deadlock the child), so each arm creates its own runtime and the daemon +/// child builds its runtime after the fork. Errors from the child still +/// reach the user through the daemonize readiness pipe. +fn mount_nfs(args: MountArgs) -> Result<()> { + if !args.mountpoint.exists() { + anyhow::bail!("Mountpoint does not exist: {}", args.mountpoint.display()); + } + let mountpoint = std::fs::canonicalize(args.mountpoint.clone())?; + + if args.foreground { + let rt = crate::get_runtime(); + rt.block_on(mount_nfs_backend(args, mountpoint)) + } else { + let ready_mountpoint = mountpoint.clone(); + agentfs_mount::daemon::daemonize( + move || { + // The forked daemon shares the parent's spill dir, which the + // parent removes when it exits; move off it before DB work. + crate::config::adopt_private_spill_dir(); + let rt = crate::get_runtime(); + rt.block_on(mount_nfs_backend(args, mountpoint)) + }, + move || agentfs_mount::is_mountpoint(&ready_mountpoint), + std::time::Duration::from_secs(10), + ) } } /// Mount the agent filesystem using FUSE (Linux only). #[cfg(target_os = "linux")] fn mount_fuse(args: MountArgs) -> Result<()> { - let opts = AgentFSOptions::resolve(&args.id_or_path)?; + let opts = apply_encryption(AgentFSOptions::resolve(&args.id_or_path)?, &args.encryption); // Check schema version before daemonizing. This allows us to show the error // message to the user directly, rather than having it appear in daemon logs. { let rt = crate::get_runtime(); let db_path = opts.db_path()?; - let result: Result<(), SdkError> = rt.block_on(async { - let db = turso::Builder::new_local(&db_path).build().await?; - let conn = db.connect()?; - agentfs_sdk::schema::check_schema_version(&conn).await?; - Ok(()) - }); - if let Err(SdkError::SchemaVersionMismatch { found, expected }) = result { - exit_schema_version_mismatch(&found, &expected, &args.id_or_path); + let result = rt.block_on(require_schema_current_for_mount_precheck( + &db_path, + args.encryption.as_ref(), + )); + if let Err(err @ SdkError::SchemaVersionMismatch { .. }) = result { + return Err(super::migrate::open_error_with_guidance( + err, + &args.id_or_path, + )); } } @@ -127,29 +156,27 @@ fn mount_fuse(args: MountArgs) -> Result<()> { .ino() }; - let fuse_opts = FuseMountOptions { + let mount_opts = MountOpts { mountpoint: args.mountpoint.clone(), + backend: Backend::Fuse, auto_unmount: args.auto_unmount, allow_root: args.allow_root, allow_other: args.allow_other, fsname, uid: args.uid, gid: args.gid, + lazy_unmount: true, + timeout: std::time::Duration::from_secs(10), }; let id_or_path = args.id_or_path.clone(); let foreground = args.foreground; let partial_origin_policy = args.partial_origin_policy; - let mountpoint_for_shutdown = mountpoint.clone(); let mount = move || { let rt = crate::get_runtime(); - let agentfs = match rt.block_on(open_agentfs(opts)) { - Ok(fs) => fs, - Err(SdkError::SchemaVersionMismatch { found, expected }) => { - exit_schema_version_mismatch(&found, &expected, &id_or_path); - } - Err(e) => return Err(e.into()), - }; + let agentfs = rt + .block_on(open_agentfs(opts)) + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; // Check for overlay configuration let fs: Arc = rt.block_on(async { @@ -193,54 +220,47 @@ fn mount_fuse(args: MountArgs) -> Result<()> { } })?; - // Run the session on its own thread so termination signals can tear - // the mount down; the default disposition would kill the process - // without unmounting, stranding a dead mount table entry. - let session = std::thread::spawn(move || crate::fuse::mount(fs, fuse_opts, rt)); - let interrupted = crate::get_runtime().block_on(async { - tokio::select! { - result = crate::mount::shutdown_signal() => result.map(|_| true), - _ = async { - loop { - tokio::time::sleep(std::time::Duration::from_millis(250)).await; - if session.is_finished() { - break; - } - } - } => Ok(false), - } - })?; - if interrupted { - let _ = unmount(&mountpoint_for_shutdown, MountBackend::Fuse, true); - } - match session.join() { - Ok(result) => result, - Err(panic) => Err(anyhow::anyhow!("FUSE session thread panicked: {panic:?}")), - } + rt.block_on(run_mount_session(fs, mount_opts, foreground)) }; if foreground { mount() } else { - crate::daemon::daemonize( - mount, - move || is_mounted(&mountpoint), + agentfs_mount::daemon::daemonize( + move || { + // The forked daemon shares the parent's spill dir, which the + // parent removes when it exits; move off it before DB work. + crate::config::adopt_private_spill_dir(); + mount() + }, + move || agentfs_mount::is_mountpoint(&mountpoint), std::time::Duration::from_secs(10), ) } } +/// Open the database for the pre-daemonize schema check with the same keyed +/// open as the real mount, so an encrypted CURRENT database classifies as +/// current instead of failing before classification. +#[cfg(target_os = "linux")] +async fn require_schema_current_for_mount_precheck( + db_path: &str, + encryption: Option<&(String, String)>, +) -> std::result::Result<(), SdkError> { + let db = super::safety::build_local_database(Path::new(db_path), encryption) + .await + .map_err(|err| SdkError::Internal(format!("{err:#}")))?; + let conn = db.connect()?; + agentfs_core::schema::require_current(&conn).await +} + /// Mount the agent filesystem using NFS over localhost. -async fn mount_nfs_backend(args: MountArgs) -> Result<()> { +/// +/// `mountpoint` is pre-canonicalized by `mount_nfs` before any fork. +async fn mount_nfs_backend(args: MountArgs, mountpoint: PathBuf) -> Result<()> { use crate::cmd::init::open_agentfs; - let opts = AgentFSOptions::resolve(&args.id_or_path)?; - - if !args.mountpoint.exists() { - anyhow::bail!("Mountpoint does not exist: {}", args.mountpoint.display()); - } - - let mountpoint = std::fs::canonicalize(args.mountpoint.clone())?; + let opts = apply_encryption(AgentFSOptions::resolve(&args.id_or_path)?, &args.encryption); let fsname = format!( "agentfs:{}", @@ -250,13 +270,9 @@ async fn mount_nfs_backend(args: MountArgs) -> Result<()> { ); // Open AgentFS - let agentfs = match open_agentfs(opts).await { - Ok(fs) => fs, - Err(SdkError::SchemaVersionMismatch { found, expected }) => { - exit_schema_version_mismatch(&found, &expected, &args.id_or_path); - } - Err(e) => return Err(e.into()), - }; + let agentfs = open_agentfs(opts) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &args.id_or_path))?; // Check for overlay configuration // Query base_path in a separate scope so connection is released before load_whiteouts @@ -297,149 +313,34 @@ async fn mount_nfs_backend(args: MountArgs) -> Result<()> { Arc::new(agentfs.fs) as Arc }; - if args.foreground { - // Use the unified mount API for foreground mode - let mount_opts = MountOpts { - mountpoint: mountpoint.clone(), - backend: MountBackend::Nfs, - fsname, - uid: args.uid, - gid: args.gid, - allow_other: args.allow_other, - allow_root: args.allow_root, - auto_unmount: args.auto_unmount, - lazy_unmount: true, - timeout: std::time::Duration::from_secs(10), - }; - - let _mount_handle = mount_fs(fs, mount_opts).await?; - - eprintln!("Mounted at {}", mountpoint.display()); - eprintln!("Press Ctrl+C to unmount and exit."); - crate::mount::shutdown_signal().await?; - - // Handle drops automatically when we exit this scope - } else { - // Daemon mode: use manual NFS server setup for persistent background operation - let nfs = AgentNFS::new(fs); - let port = find_available_port(DEFAULT_NFS_PORT)?; - - let bind_addr = format!("127.0.0.1:{}", port); - let listener = crate::nfsserve::tcp::NFSTcpListener::bind(&bind_addr, nfs) - .await - .context("Failed to bind NFS server")?; - - eprintln!("Starting NFS server on 127.0.0.1:{}", port); - - tokio::spawn(async move { - if let Err(e) = listener.handle_forever().await { - eprintln!("NFS server error: {}", e); - } - }); - - tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - nfs_mount(port, &mountpoint)?; - - eprintln!("Mounted at {}", mountpoint.display()); - eprintln!( - "Running in background. Use 'umount {}' to unmount.", - mountpoint.display() - ); - - // Block forever (server runs in background task) - std::future::pending::<()>().await; - } - - Ok(()) -} - -/// Find an available TCP port starting from the given port. -fn find_available_port(start_port: u32) -> Result { - for port in start_port..start_port + 100 { - if std::net::TcpListener::bind(format!("127.0.0.1:{}", port)).is_ok() { - return Ok(port); - } - } - anyhow::bail!( - "Could not find an available port in range {}-{}", - start_port, - start_port + 100 - ); -} - -/// Mount the NFS filesystem (Linux version). -#[cfg(target_os = "linux")] -fn nfs_mount(port: u32, mountpoint: &Path) -> Result<()> { - let output = Command::new("mount") - .args([ - "-t", - "nfs", - "-o", - &format!( - "vers=3,tcp,port={},mountport={},nolock,soft,timeo=10,retrans=2", - port, port - ), - "127.0.0.1:/", - mountpoint.to_str().unwrap(), - ]) - .output() - .context("Failed to execute mount command")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!( - "Failed to mount NFS: {}. Make sure NFS client tools are installed (nfs-common on Debian/Ubuntu, nfs-utils on Fedora/RHEL) and you have permission to mount (try running with sudo).", - stderr.trim() - ); - } + let mount_opts = MountOpts { + mountpoint: mountpoint.clone(), + backend: Backend::Nfs, + fsname, + uid: args.uid, + gid: args.gid, + allow_other: args.allow_other, + allow_root: args.allow_root, + auto_unmount: args.auto_unmount, + lazy_unmount: true, + timeout: std::time::Duration::from_secs(10), + }; - Ok(()) + run_mount_session(fs, mount_opts, args.foreground).await } -/// Mount the NFS filesystem (macOS version). -#[cfg(target_os = "macos")] -fn nfs_mount(port: u32, mountpoint: &Path) -> Result<()> { - let output = Command::new("/sbin/mount_nfs") - .args([ - "-o", - &format!( - "locallocks,vers=3,tcp,port={},mountport={},wsize=1048576,rsize=1048576,soft,timeo=10,retrans=2", - port, port - ), - "127.0.0.1:/", - mountpoint.to_str().unwrap(), - ]) - .output() - .context("Failed to execute mount_nfs")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("Failed to mount NFS: {}", stderr.trim()); +async fn run_mount_session( + fs: Arc, + mount_opts: MountOpts, + foreground: bool, +) -> Result<()> { + let handle = mount_fs(fs, mount_opts).await?; + if foreground { + eprintln!("Mounted at {}", handle.mountpoint().display()); + eprintln!("Press Ctrl+C to unmount and exit."); } - Ok(()) -} - -/// Check if a path is a mountpoint by comparing device IDs -#[cfg(target_os = "linux")] -fn is_mounted(path: &std::path::Path) -> bool { - let path_meta = match std::fs::metadata(path) { - Ok(m) => m, - Err(_) => return false, - }; - - let parent = match path.parent() { - Some(p) if !p.as_os_str().is_empty() => p, - _ => std::path::Path::new("/"), - }; - - let parent_meta = match std::fs::metadata(parent) { - Ok(m) => m, - Err(_) => return false, - }; - - // Different device IDs means it's a mountpoint - path_meta.dev() != parent_meta.dev() + agentfs_mount::supervise::supervise_mount(handle).await } /// List all currently mounted agentfs filesystems (Linux) @@ -650,18 +551,196 @@ pub fn prune_mounts(_force: bool) -> Result<()> { anyhow::bail!("Mount pruning is only available on Linux") } -/// Print schema version mismatch error and exit. -fn exit_schema_version_mismatch(found: &str, expected: &str, id_or_path: &str) -> ! { - eprintln!("Error: Filesystem `{}` requires migration", id_or_path); - eprintln!(); - eprintln!( - "Found schema version {}, but this version of agentfs requires {}.", - found, expected - ); - eprintln!(); - eprintln!("To upgrade, run:"); - eprintln!(); - eprintln!(" agentfs migrate {}", id_or_path); - eprintln!(); - std::process::exit(1); +#[cfg(all(test, target_os = "linux"))] +mod tests { + use super::*; + use std::path::Path; + use tempfile::tempdir; + use turso::{Builder, Connection}; + + #[tokio::test] + async fn mount_precheck_backfills_legacy_whiteout_parent_path() { + let dir = tempdir().unwrap(); + let db_path = dir.path().join("legacy-whiteout.db"); + create_currentish_db_with_legacy_whiteout(&db_path).await; + + require_schema_current_for_mount_precheck(db_path.to_str().unwrap(), None) + .await + .unwrap(); + + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await + .unwrap(); + let conn = db.connect().unwrap(); + let columns = table_columns(&conn, "fs_whiteout").await; + assert!( + columns.iter().any(|column| column == "parent_path"), + "mount precheck did not add fs_whiteout.parent_path; columns={columns:?}" + ); + let mut rows = conn + .query( + "SELECT parent_path, created_at FROM fs_whiteout WHERE path = '/dir/deleted'", + (), + ) + .await + .unwrap(); + let row = rows.next().await.unwrap().unwrap(); + let parent_path = row.get::(0).unwrap(); + let created_at = row.get::(1).unwrap(); + println!( + "mount precheck: fs_whiteout columns={columns:?}; /dir/deleted parent_path={parent_path}" + ); + assert_eq!(parent_path, "/dir"); + assert_eq!(created_at, 123); + } + + #[tokio::test] + async fn mount_precheck_opens_encrypted_current_db_with_key() { + const TEST_KEY: &str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + const TEST_CIPHER: &str = "aes256gcm"; + + let dir = tempdir().unwrap(); + let db_path = dir.path().join("encrypted.db"); + let agentfs = agentfs_core::AgentFS::open( + AgentFSOptions::with_path(db_path.to_str().unwrap().to_string()).with_encryption( + EncryptionConfig { + hex_key: TEST_KEY.to_string(), + cipher: TEST_CIPHER.to_string(), + }, + ), + ) + .await + .unwrap(); + drop(agentfs); + + let encryption = (TEST_KEY.to_string(), TEST_CIPHER.to_string()); + require_schema_current_for_mount_precheck(db_path.to_str().unwrap(), Some(&encryption)) + .await + .expect("keyed precheck must classify an encrypted CURRENT database as current"); + + let err = require_schema_current_for_mount_precheck(db_path.to_str().unwrap(), None) + .await + .expect_err("keyless precheck of an encrypted database must fail, not classify"); + assert!( + !matches!(err, SdkError::SchemaVersionMismatch { .. }), + "keyless open must not misclassify as a schema mismatch: {err}" + ); + } + + async fn create_currentish_db_with_legacy_whiteout(db_path: &Path) { + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await + .unwrap(); + let conn = db.connect().unwrap(); + conn.execute( + "CREATE TABLE fs_inode ( + ino INTEGER PRIMARY KEY AUTOINCREMENT, + mode INTEGER NOT NULL, + nlink INTEGER NOT NULL DEFAULT 0, + uid INTEGER NOT NULL DEFAULT 0, + gid INTEGER NOT NULL DEFAULT 0, + size INTEGER NOT NULL DEFAULT 0, + atime INTEGER NOT NULL, + mtime INTEGER NOT NULL, + ctime INTEGER NOT NULL, + rdev INTEGER NOT NULL DEFAULT 0, + atime_nsec INTEGER NOT NULL DEFAULT 0, + mtime_nsec INTEGER NOT NULL DEFAULT 0, + ctime_nsec INTEGER NOT NULL DEFAULT 0, + data_inline BLOB, + storage_kind INTEGER NOT NULL DEFAULT 0 + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_dentry ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parent_ino INTEGER NOT NULL, + ino INTEGER NOT NULL, + UNIQUE(parent_ino, name) + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_data ( + ino INTEGER NOT NULL, + chunk_index INTEGER NOT NULL, + data BLOB NOT NULL, + PRIMARY KEY (ino, chunk_index) + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_symlink ( + ino INTEGER PRIMARY KEY, + target TEXT NOT NULL + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_whiteout ( + path TEXT PRIMARY KEY, + created_at INTEGER NOT NULL + )", + (), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO fs_whiteout (path, created_at) VALUES ('/dir/deleted', 123)", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE kv_store ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + created_at INTEGER DEFAULT (unixepoch()), + updated_at INTEGER DEFAULT (unixepoch()) + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE tool_calls ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parameters TEXT, + result TEXT, + error TEXT, + status TEXT NOT NULL DEFAULT 'pending', + started_at INTEGER NOT NULL, + completed_at INTEGER, + duration_ms INTEGER + )", + (), + ) + .await + .unwrap(); + } + + async fn table_columns(conn: &Connection, table_name: &str) -> Vec { + let mut rows = conn + .query(&format!("PRAGMA table_info({table_name})"), ()) + .await + .unwrap(); + let mut columns = Vec::new(); + while let Some(row) = rows.next().await.unwrap() { + columns.push(row.get::(1).unwrap()); + } + columns + } } diff --git a/cli/src/cmd/nfs.rs b/crates/agentfs-cli/src/cmd/nfs.rs similarity index 71% rename from cli/src/cmd/nfs.rs rename to crates/agentfs-cli/src/cmd/nfs.rs index 507b0b79..cfa7421e 100644 --- a/cli/src/cmd/nfs.rs +++ b/crates/agentfs-cli/src/cmd/nfs.rs @@ -4,17 +4,22 @@ //! filesystem over the network, allowing remote systems (like VMs) to mount //! it as their root filesystem. -use agentfs_sdk::{agentfs_dir, AgentFSOptions, FileSystem, HostFS, OverlayFS}; +use agentfs_core::{agentfs_dir, AgentFSOptions, EncryptionConfig, FileSystem, HostFS, OverlayFS}; +use agentfs_mount::{serve_nfs, NfsServerOptions}; use anyhow::{Context, Result}; use std::path::PathBuf; use std::sync::Arc; use tokio::signal; use crate::cmd::init::open_agentfs; -use crate::nfs::AgentNFS; /// Handle the `nfs` command - start a standalone NFS server. -pub async fn handle_nfs_command(id_or_path: String, bind: String, port: u32) -> Result<()> { +pub async fn handle_nfs_command( + id_or_path: String, + bind: String, + port: u32, + encryption: Option<(String, String)>, +) -> Result<()> { // Resolve database path let db_path = resolve_db_path(&id_or_path)?; @@ -23,8 +28,16 @@ pub async fn handle_nfs_command(id_or_path: String, bind: String, port: u32) -> .to_str() .context("Database path contains non-UTF8 characters")?; - let options = AgentFSOptions::with_path(db_path_str); - let agentfs = open_agentfs(options).await?; + let mut options = AgentFSOptions::with_path(db_path_str); + if let Some((key, cipher)) = encryption { + options = options.with_encryption(EncryptionConfig { + hex_key: key, + cipher, + }); + } + let agentfs = open_agentfs(options) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; // Check if overlay is configured in the database let base_path = agentfs @@ -45,39 +58,29 @@ pub async fn handle_nfs_command(id_or_path: String, bind: String, port: u32) -> Arc::new(agentfs.fs) }; - // Create NFS adapter - let nfs = AgentNFS::new(fs); - // Bind NFS server - let bind_addr_str = format!("{}:{}", bind, port); - let listener = crate::nfsserve::tcp::NFSTcpListener::bind(&bind_addr_str, nfs) + let server_handle = serve_nfs(fs, NfsServerOptions::new(bind.clone(), port)) .await - .with_context(|| format!("Failed to bind NFS server to {}", bind_addr_str))?; + .with_context(|| format!("Failed to bind NFS server to {bind}:{port}"))?; + let listen_addr = server_handle.local_addr(); + let listen_port = u32::from(server_handle.local_port()); // Print server info eprintln!(); eprintln!("AgentFS NFS Server"); eprintln!(" Database: {}", db_path.display()); - eprintln!(" Listening: {}", bind_addr_str); + eprintln!(" Listening: {}", listen_addr); eprintln!(" Export: /"); eprintln!(); eprintln!("Mount from client:"); eprintln!( " mount -t nfs -o vers=3,tcp,port={},mountport={},nolock {}:/ /mnt", - port, port, bind + listen_port, listen_port, bind ); eprintln!(); eprintln!("Press Ctrl+C to stop."); eprintln!(); - // Spawn the NFS server task - use crate::nfsserve::tcp::NFSTcp; - let server_handle = tokio::spawn(async move { - if let Err(e) = listener.handle_forever().await { - eprintln!("NFS server error: {}", e); - } - }); - // Wait for Ctrl+C signal::ctrl_c() .await @@ -86,8 +89,11 @@ pub async fn handle_nfs_command(id_or_path: String, bind: String, port: u32) -> eprintln!(); eprintln!("Shutting down..."); - // Abort the server task (no graceful shutdown support) - server_handle.abort(); + server_handle.cancel(); + server_handle + .join() + .await + .context("NFS server shutdown failed")?; Ok(()) } diff --git a/crates/agentfs-cli/src/cmd/profiling.rs b/crates/agentfs-cli/src/cmd/profiling.rs new file mode 100644 index 00000000..6bfcb318 --- /dev/null +++ b/crates/agentfs-cli/src/cmd/profiling.rs @@ -0,0 +1,56 @@ +//! CLI-owned profile sink helpers. +//! +//! Adapter-specific counters register from their owning crates when those +//! adapters are constructed; the CLI owns only the process report lifecycle. + +pub use agentfs_core::telemetry::ProfileSnapshot; + +const PROFILE_SUMMARY_EVENT: &str = "agentfs_profile_summary"; + +/// Drop guard installed by the CLI binary for a single process summary. +#[derive(Debug)] +pub struct ProfileReportGuard { + source: &'static str, +} + +impl ProfileReportGuard { + fn new(source: &'static str) -> Self { + Self { source } + } + + pub fn emit_now(&self) { + emit_profile_summary(self.source); + } +} + +impl Drop for ProfileReportGuard { + fn drop(&mut self) { + self.emit_now(); + } +} + +pub fn install_cli_sink() -> ProfileReportGuard { + ProfileReportGuard::new("cli") +} + +pub fn emit_cli_report() { + emit_profile_summary("cli"); +} + +pub fn report_checkpoint() { + if let Some(payload) = agentfs_core::telemetry::checkpoint_payload(PROFILE_SUMMARY_EVENT) { + eprintln!("{payload}"); + } +} + +fn emit_profile_summary(source: &str) { + if let Some(payload) = + agentfs_core::telemetry::take_summary_payload(PROFILE_SUMMARY_EVENT, source) + { + eprintln!("{payload}"); + } +} + +pub fn snapshot() -> ProfileSnapshot { + agentfs_core::telemetry::snapshot() +} diff --git a/cli/src/cmd/ps.rs b/crates/agentfs-cli/src/cmd/ps.rs similarity index 88% rename from cli/src/cmd/ps.rs rename to crates/agentfs-cli/src/cmd/ps.rs index 752bef91..f49aaca2 100644 --- a/cli/src/cmd/ps.rs +++ b/crates/agentfs-cli/src/cmd/ps.rs @@ -65,8 +65,20 @@ pub fn remove_proc_file(session_id: &str) { } /// Check if a process is still running. +/// +/// `kill(pid, 0)` probes liveness without signalling; EPERM still proves the +/// pid exists. This works on every Unix (a /proc scan would be Linux-only). +#[cfg(unix)] fn is_process_alive(pid: u32) -> bool { - PathBuf::from(format!("/proc/{}", pid)).exists() + let result = unsafe { libc::kill(pid as libc::pid_t, 0) }; + result == 0 || std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM) +} + +/// `agentfs run` sessions only exist on Unix platforms, so nothing can be +/// alive here. +#[cfg(not(unix))] +fn is_process_alive(_pid: u32) -> bool { + false } /// Information about a session with its processes. @@ -183,6 +195,19 @@ fn truncate(s: &str, max_len: usize) -> String { } } +#[cfg(all(test, unix))] +mod liveness { + #[test] + fn probe_distinguishes_live_and_reaped_pids() { + assert!(super::is_process_alive(std::process::id())); + + let mut child = std::process::Command::new("true").spawn().unwrap(); + let pid = child.id(); + child.wait().unwrap(); + assert!(!super::is_process_alive(pid)); + } +} + // Column widths for table output const COL_SESSION: usize = 36; const COL_PID: usize = 8; diff --git a/crates/agentfs-cli/src/cmd/run/darwin.rs b/crates/agentfs-cli/src/cmd/run/darwin.rs new file mode 100644 index 00000000..11c80d6d --- /dev/null +++ b/crates/agentfs-cli/src/cmd/run/darwin.rs @@ -0,0 +1,589 @@ +//! Darwin (macOS) run command implementation. +//! +//! This module provides a sandboxed execution environment using NFS for +//! filesystem mounting. The current working directory becomes a +//! copy-on-write overlay backed by AgentFS, mounted via a localhost NFS server. +//! +//! Sandboxing is enforced using macOS sandbox-exec with dynamically generated +//! profiles. Writes are restricted to the NFS mountpoint and allowed paths; +//! reads are default-deny and limited to the session paths, the allowed +//! paths, and a curated set of platform roots the runtime needs (see +//! `PLATFORM_READ_ROOTS`). + +use crate::opts::RunOptions; +use agentfs_core::{AgentFS, AgentFSOptions, FileSystem, HostFS, OverlayFS}; +use anyhow::{Context, Result}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use agentfs_mount::supervise::{ + exit_code_for_spawn_error, exit_code_for_status, run_supervised, supervise_command, + ChildOutcome, +}; +use agentfs_mount::{mount_fs, Backend, MountOpts}; + +/// Configuration for the macOS sandbox profile. +#[derive(Debug, Clone)] +pub(super) struct SandboxConfig { + /// The NFS mountpoint (primary read/write location). + pub(super) mountpoint: PathBuf, + /// Additional paths to allow read/write access. + pub(super) allow_paths: Vec, + /// Whether to allow network access. + pub(super) allow_network: bool, + /// Session ID for log filtering. + pub(super) session_id: String, +} + +/// System roots that stay readable under the default-deny read posture. +/// +/// Curated from the Codex restricted platform defaults and the +/// Chromium/Mozilla Seatbelt profiles: the dyld shared cache cryptex, +/// system libraries, executable directories, package managers, system +/// config (`/private/etc`), terminfo/locale data (`/usr/share`), temp +/// directories (already writable below), and `/dev` essentials. `/System` +/// is handled separately: `/System/Volumes` firmlinks back into the data +/// volume, so a plain `(subpath "/System")` would reopen every read the +/// posture is meant to deny. +pub(super) const PLATFORM_READ_ROOTS: &[&str] = &[ + "/Applications", + "/Library/Apple", + "/Library/Preferences", + "/System/Volumes/Preboot/Cryptexes", + "/bin", + "/dev", + "/etc", + "/opt", + "/private/etc", + "/private/tmp", + "/private/var/db", + "/private/var/folders", + "/private/var/select", + "/private/var/tmp", + "/sbin", + "/tmp", + "/usr/bin", + "/usr/lib", + "/usr/libexec", + "/usr/local", + "/usr/sbin", + "/usr/share", + "/var/tmp", +]; + +/// Standard symlinks and firmlink components that need metadata access for +/// path resolution (`/var` -> `/private/var`, the `/System/Volumes/Data` +/// firmlink chain, the `/System/Volumes/Preboot` ancestor of the dyld +/// cryptex read root), mirroring the Codex restricted defaults. +const PLATFORM_METADATA_PATHS: &[&str] = &[ + "/System/Volumes", + "/System/Volumes/Data", + "/System/Volumes/Data/Users", + "/System/Volumes/Data/private", + "/System/Volumes/Preboot", + "/etc", + "/private/etc/localtime", + "/tmp", + "/var", +]; + +/// Proper ancestors of every read root (excluding `/`), deduplicated and +/// sorted, so path resolution can stat each component on the way down +/// without granting data reads outside the roots themselves. +fn read_root_ancestors(roots: &[PathBuf]) -> Vec { + let mut ancestors = std::collections::BTreeSet::new(); + for root in roots { + for ancestor in root.ancestors().skip(1) { + if ancestor != Path::new("/") && !ancestor.as_os_str().is_empty() { + ancestors.insert(ancestor.to_path_buf()); + } + } + } + ancestors.into_iter().collect() +} + +/// A generated Seatbelt policy plus the dynamic path parameters it +/// references. +/// +/// Dynamic paths (session, allow-listed, home) never appear in the SBPL +/// text: rules reference them as `(param "NAME")` and the values are passed +/// to `/usr/bin/sandbox-exec` as `-D NAME=value` definitions, so a path +/// containing SBPL metacharacters (a double quote) cannot corrupt the +/// profile. This is the Chromium/Codex parameter model +/// (research/darwin-seatbelt-read-scoping.md). +#[derive(Debug, Clone)] +pub(super) struct SandboxProfile { + pub(super) policy: String, + pub(super) params: Vec<(String, PathBuf)>, +} + +/// Dynamic-path parameter table, deduplicated by value so a path referenced +/// by several rules (e.g. the mountpoint's read and write allows) is defined +/// exactly once on the sandbox-exec command line. +#[derive(Default)] +struct SandboxParams(Vec<(String, PathBuf)>); + +impl SandboxParams { + fn define(&mut self, name: impl Into, path: &Path) -> String { + if let Some((existing, _)) = self.0.iter().find(|(_, value)| value == path) { + return existing.clone(); + } + let name = name.into(); + debug_assert!(self.0.iter().all(|(existing, _)| existing != &name)); + self.0.push((name.clone(), path.to_path_buf())); + name + } +} + +/// The session id reaches the profile text itself (deny message and log-tag +/// comment) where Seatbelt parameters are not available, so restrict it to a +/// conservative charset instead of trusting `--session` input. A session id +/// with no surviving characters falls back to a fixed placeholder so the +/// deny message never degrades to a bare `agentfs-:` tag. +fn sandbox_log_tag(session_id: &str) -> String { + let tag: String = session_id + .chars() + .filter(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.')) + .collect(); + if tag.is_empty() { + "session".to_string() + } else { + tag + } +} + +/// Generate a sandbox-exec profile for AgentFS. +/// +/// The profile allows most operations but restricts file writes to the NFS +/// mountpoint, temp directories, and explicitly allowed paths, and file +/// reads to the session paths, allowed paths, and `PLATFORM_READ_ROOTS`. +/// Every dynamic path is emitted as a `(param "NAME")` reference; the values +/// travel out-of-band in [`SandboxProfile::params`]. +pub(super) fn generate_sandbox_profile(config: &SandboxConfig) -> SandboxProfile { + let mut profile = Vec::new(); + let mut params = SandboxParams::default(); + let log_tag = format!("agentfs-{}", sandbox_log_tag(&config.session_id)); + + profile.push("(version 1)".to_string()); + profile.push(format!( + r#"(deny default (with message "{log_tag}: access denied"))"# + )); + profile.push(format!("; Log tag: {}", log_tag)); + + profile.push("; Allow most operations".to_string()); + profile.push("(allow process*)".to_string()); + profile.push("(allow signal)".to_string()); + profile.push("(allow mach*)".to_string()); + profile.push("(allow sysctl*)".to_string()); + profile.push("(allow system*)".to_string()); + profile.push("(allow ipc*)".to_string()); + profile.push("(allow pseudo-tty)".to_string()); + + profile.push( + "; Readable paths: session and allow-listed roots (reads are default-deny)".to_string(), + ); + let mut read_roots = vec![config.mountpoint.clone()]; + let mountpoint_param = params.define("MOUNTPOINT", &config.mountpoint); + let mut read_root_params = vec![mountpoint_param.clone()]; + let mut run_dir_param = None; + if let Some(parent) = config.mountpoint.parent() { + read_roots.push(parent.to_path_buf()); + let name = params.define("RUN_DIR", parent); + read_root_params.push(name.clone()); + run_dir_param = Some(name); + } + let mut allow_path_params = Vec::new(); + for (index, path) in config.allow_paths.iter().enumerate() { + read_roots.push(path.clone()); + let name = params.define(format!("ALLOW_PATH_{index}"), path); + read_root_params.push(name.clone()); + allow_path_params.push(name); + } + for name in &read_root_params { + profile.push(format!( + r#"(allow file-read* file-map-executable file-test-existence (subpath (param "{name}")))"# + )); + } + + profile + .push("; Platform read roots (loader, frameworks, executables, config, temp)".to_string()); + profile.push(r#"(allow file-read* file-test-existence (literal "/"))"#.to_string()); + profile.push( + r#"(allow file-read* file-map-executable file-test-existence (require-all (subpath "/System") (require-not (subpath "/System/Volumes"))))"# + .to_string(), + ); + for root in PLATFORM_READ_ROOTS { + profile.push(format!( + r#"(allow file-read* file-map-executable file-test-existence (subpath "{root}"))"# + )); + } + + profile + .push("; Metadata for path resolution (symlinks, firmlinks, root ancestors)".to_string()); + for path in PLATFORM_METADATA_PATHS { + profile.push(format!( + r#"(allow file-read-metadata file-test-existence (literal "{path}"))"# + )); + } + for (index, ancestor) in read_root_ancestors(&read_roots).iter().enumerate() { + let name = params.define(format!("ANCESTOR_{index}"), ancestor); + profile.push(format!( + r#"(allow file-read-metadata file-test-existence (literal (param "{name}")))"# + )); + } + + profile.push("; Writable paths".to_string()); + profile.push(format!( + r#"(allow file-write* (subpath (param "{mountpoint_param}")))"# + )); + + if let Some(name) = &run_dir_param { + profile.push(format!(r#"(allow file-write* (subpath (param "{name}")))"#)); + } + + profile.push(r#"(allow file-write* (subpath "/private/tmp"))"#.to_string()); + profile.push(r#"(allow file-write* (subpath "/tmp"))"#.to_string()); + profile.push(r#"(allow file-write* (subpath "/var/tmp"))"#.to_string()); + profile.push(r#"(allow file-write* (subpath "/private/var/folders"))"#.to_string()); + profile.push(r#"(allow file-write* (subpath "/dev"))"#.to_string()); + profile.push(r#"(allow file-ioctl (subpath "/dev"))"#.to_string()); + + for name in &allow_path_params { + profile.push(format!(r#"(allow file-write* (subpath (param "{name}")))"#)); + } + + profile.push("; Network".to_string()); + if config.allow_network { + profile.push("(allow network*)".to_string()); + } else { + profile.push(r#"(allow network* (remote ip "localhost:*"))"#.to_string()); + profile.push(r#"(allow network* (local ip "localhost:*"))"#.to_string()); + } + + profile.push("; Security and Keychain".to_string()); + profile.push(r#"(allow file-write* (subpath "/private/var/db/mds"))"#.to_string()); + profile.push( + r#"(allow file-write* (regex #"^/private/var/folders/[^/]+/[^/]+/C/mds/"))"#.to_string(), + ); + profile + .push(r#"(allow file-write* (regex #"^/private/var/folders/[^/]+/[^/]+/T/"))"#.to_string()); + if let Some(home) = dirs::home_dir() { + let name = params.define("HOME_LIBRARY", &home.join("Library")); + profile.push(format!(r#"(allow file-write* (subpath (param "{name}")))"#)); + } + profile.push(r#"(allow file-write* (subpath "/Library/Preferences"))"#.to_string()); + profile.push(r#"(allow file-write* (subpath "/Library/Keychains"))"#.to_string()); + profile.push("(allow authorization-right-obtain)".to_string()); + profile.push("(allow user-preference-write)".to_string()); + profile.push("(allow user-preference-read)".to_string()); + + SandboxProfile { + policy: profile.join("\n"), + params: params.0, + } +} + +/// Missing / non-executable commands pass through as 127/126 like the +/// shared exec path (VAL-CLI-019 exception); every other supervision error +/// goes to the unified reporter. +pub(super) fn spawn_error_exit_code(error: &anyhow::Error) -> Option { + error + .downcast_ref::() + .and_then(exit_code_for_spawn_error) +} + +/// Run the command in a Darwin sandbox. +pub async fn run(options: RunOptions) -> Result<()> { + let RunOptions { + allow, + no_default_allows, + session, + system: _, + encryption, + partial_origin_policy, + command, + args, + } = options; + let cwd = std::env::current_dir().context("Failed to get current directory")?; + let home = dirs::home_dir().context("Failed to get home directory")?; + + let session = setup_run_directory(session, allow, no_default_allows, &cwd, &home)?; + + // Check if we're joining an existing session + if is_mountpoint(&session.mountpoint) { + if is_mount_healthy(&session.mountpoint) { + eprintln!("Joining existing session: {}", session.session_id); + eprintln!(); + let outcome = run_command_in_mount(&session, command, args).await?; + crate::profiling::emit_cli_report(); + std::process::exit(exit_code_for_outcome(outcome)); + } else { + eprintln!("Cleaning up stale NFS mount..."); + if let Err(e) = agentfs_mount::unmount(&session.mountpoint, Backend::Nfs, true) { + eprintln!("Warning: Failed to unmount stale mount: {}", e); + } + } + } + + // Initialize the AgentFS database + let db_path_str = session + .db_path + .to_str() + .context("Database path contains non-UTF8 characters")?; + + let encrypted = encryption.is_some(); + let mut options = AgentFSOptions::with_path(db_path_str) + .with_core_config(crate::config::core_config_from_env()); + if let Some(encryption) = encryption { + options = options.with_encryption(encryption); + } + let agentfs = AgentFS::open(options) + .await + .map_err(|err| crate::cmd::migrate::open_error_with_guidance(err, db_path_str)) + .context("Failed to create AgentFS")?; + + // Create overlay filesystem with CWD as base + let base_str = cwd.to_string_lossy().to_string(); + let hostfs = HostFS::new(&base_str).context("Failed to create HostFS")?; + let overlay = if let Some(policy) = partial_origin_policy { + OverlayFS::new_with_partial_origin_policy(Arc::new(hostfs), agentfs.fs, policy) + } else { + OverlayFS::new(Arc::new(hostfs), agentfs.fs) + }; + + // Initialize the overlay (copies directory structure) + overlay + .init(&base_str) + .await + .context("Failed to initialize overlay")?; + + let fs: Arc = Arc::new(overlay); + + let mut mount_opts = MountOpts::new(session.mountpoint.clone(), Backend::Nfs); + mount_opts.fsname = format!("agentfs:{}", session.session_id); + mount_opts.lazy_unmount = true; + mount_opts.timeout = std::time::Duration::from_secs(10); + let mount_handle = mount_fs(fs, mount_opts).await?; + + print_welcome_banner(&session, encrypted); + + let command_display = command.display().to_string(); + let child_command = command_in_mount(&session, command, args); + let status = run_supervised(mount_handle, child_command).await; + if let Err(e) = std::fs::remove_dir(&session.mountpoint) { + eprintln!( + "Warning: Failed to clean up mountpoint {}: {}", + session.mountpoint.display(), + e + ); + } + + // Print session info for the user + eprintln!(); + eprintln!("Session: {}", session.session_id); + eprintln!(); + eprintln!("To resume this session:"); + eprintln!(" agentfs run --session {}", session.session_id); + eprintln!(); + eprintln!("To see what changed:"); + eprintln!(" agentfs diff {}", session.session_id); + + crate::profiling::emit_cli_report(); + match status { + Ok(status) => std::process::exit(exit_code_for_status(status)), + Err(error) => { + if let Some(code) = spawn_error_exit_code(&error) { + eprintln!("Error: Failed to execute: {command_display}: {error}"); + std::process::exit(code); + } + Err(error.context(format!( + "Darwin/NFS run supervision failed for {command_display}" + ))) + } + } +} + +fn exit_code_for_outcome(outcome: ChildOutcome) -> i32 { + match outcome { + ChildOutcome::Exited(status) => status.code().unwrap_or(1), + ChildOutcome::Interrupted(signo) => 128 + signo, + } +} + +/// Print the welcome banner showing sandbox configuration (macOS). +#[cfg(target_os = "macos")] +fn print_welcome_banner(session: &RunSession, encrypted: bool) { + use super::group_paths_by_parent; + + eprintln!("Welcome to AgentFS!"); + eprintln!(); + eprintln!("The following directories are writable:"); + eprintln!(); + eprintln!(" - {} (copy-on-write)", session.cwd.display()); + eprintln!(" - /tmp"); + for grouped_path in group_paths_by_parent(&session.allow_paths) { + eprintln!(" - {}", grouped_path); + } + eprintln!(); + eprintln!("🔒 System paths are read-only."); + eprintln!("🙈 Everything else (including the rest of your home directory) is unreadable."); + if encrypted { + eprintln!("🔐 Delta layer is encrypted."); + } + eprintln!(); + eprintln!("To join this session from another terminal:"); + eprintln!(); + eprintln!(" agentfs run --session {} ", session.session_id); + eprintln!(); +} + +/// Configuration for a sandbox run session. +struct RunSession { + /// Directory containing session artifacts. + run_dir: PathBuf, + /// Path to the delta database. + db_path: PathBuf, + /// Path where NFS filesystem will be mounted. + mountpoint: PathBuf, + /// Session ID for the sandbox profile. + session_id: String, + /// Additional paths to allow write access. + allow_paths: Vec, + /// Original working directory. + cwd: PathBuf, +} + +/// Create a run directory with database and mountpoint paths. +/// +/// If `session_id` is provided, uses that as the run ID (allowing multiple +/// runs to share the same delta layer). Otherwise generates a unique UUID. +fn setup_run_directory( + session_id: Option, + user_allow_paths: Vec, + no_default_allows: bool, + cwd: &Path, + home: &Path, +) -> Result { + let run_id = session_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()); + let run_dir = home.join(".agentfs").join("run").join(&run_id); + std::fs::create_dir_all(&run_dir).context("Failed to create run directory")?; + + let db_path = run_dir.join("delta.db"); + let mountpoint = run_dir.join("mnt"); + std::fs::create_dir_all(&mountpoint).context("Failed to create mountpoint")?; + + // Build allowed paths list + let mut allow_paths = user_allow_paths; + if !no_default_allows { + allow_paths.extend(super::default_allowed_paths(home)); + } + + // Create zsh config directory with custom prompt + let zsh_dir = run_dir.join("zsh"); + std::fs::create_dir_all(&zsh_dir).context("Failed to create zsh config directory")?; + std::fs::write(zsh_dir.join(".zshrc"), "PROMPT='🤖 %~%# '\n") + .context("Failed to write zsh config")?; + + Ok(RunSession { + run_dir, + db_path, + mountpoint, + session_id: run_id, + allow_paths, + cwd: cwd.to_path_buf(), + }) +} + +/// Check if a path is a mountpoint by comparing device IDs with parent. +fn is_mountpoint(path: &Path) -> bool { + use std::os::unix::fs::MetadataExt; + + let Ok(path_meta) = std::fs::metadata(path) else { + return false; + }; + + let parent = path + .parent() + .filter(|p| !p.as_os_str().is_empty()) + .unwrap_or(Path::new("/")); + + let Ok(parent_meta) = std::fs::metadata(parent) else { + return false; + }; + + path_meta.dev() != parent_meta.dev() +} + +/// Check if a mount is healthy (not stale). +/// +/// Stale NFS mounts will fail when trying to access them. +fn is_mount_healthy(mountpoint: &Path) -> bool { + std::fs::read_dir(mountpoint).is_ok() +} + +/// Run a command with the working directory set to the mounted filesystem (macOS). +/// +/// On macOS, the command is wrapped with sandbox-exec using a Sandbox profile +/// that restricts file writes to the NFS mountpoint and allowed paths. +/// The mountpoint overlays CWD, and additional paths in HOME are made writable +/// through the allow_paths configuration. +#[cfg(target_os = "macos")] +fn command_in_mount( + session: &RunSession, + command: PathBuf, + args: Vec, +) -> tokio::process::Command { + // Generate the Sandbox profile + let config = SandboxConfig { + mountpoint: session.mountpoint.clone(), + allow_paths: session.allow_paths.clone(), + allow_network: true, + session_id: session.session_id.clone(), + }; + let profile = generate_sandbox_profile(&config); + + // Wrap the command with sandbox-exec, pinned to the system binary so a + // PATH-injected replacement cannot supplant the sandbox. Dynamic paths + // travel as -D parameter definitions, never inside the profile text. + let mut cmd = tokio::process::Command::new("/usr/bin/sandbox-exec"); + cmd.arg("-p").arg(&profile.policy); + for (name, value) in &profile.params { + let mut definition = std::ffi::OsString::from(format!("{name}=")); + definition.push(value.as_os_str()); + cmd.arg("-D").arg(definition); + } + cmd.arg(&command) + .args(&args) + .current_dir(&session.mountpoint) + .env("AGENTFS", "1") + .env("AGENTFS_SANDBOX", "macos-sandbox") + // Bash prompt - show full path since we're not changing HOME + .env("PS1", "🤖 \\w\\$ ") + // Zsh: use custom ZDOTDIR to override prompt + .env("ZDOTDIR", session.run_dir.join("zsh")); + // The CLI's private spill dir is process-internal; children keep the + // user's TMPDIR. + crate::config::restore_original_tmpdir(&mut cmd); + + cmd +} + +async fn run_command_in_mount( + session: &RunSession, + command: PathBuf, + args: Vec, +) -> Result { + let command_display = command.display().to_string(); + let child_command = command_in_mount(session, command, args); + match supervise_command(child_command).await { + Ok(outcome) => Ok(outcome), + Err(error) => { + if let Some(code) = spawn_error_exit_code(&error) { + eprintln!("Error: Failed to execute: {command_display}: {error}"); + crate::profiling::emit_cli_report(); + std::process::exit(code); + } + Err(error).with_context(|| format!("Failed to execute command: {command_display}")) + } + } +} diff --git a/cli/src/sandbox/linux.rs b/crates/agentfs-cli/src/cmd/run/linux.rs similarity index 58% rename from cli/src/sandbox/linux.rs rename to crates/agentfs-cli/src/cmd/run/linux.rs index 983cae11..98533ffe 100644 --- a/cli/src/sandbox/linux.rs +++ b/crates/agentfs-cli/src/cmd/run/linux.rs @@ -15,8 +15,9 @@ //! The HostFS base layer then accesses files through `/proc/self/fd/N`, //! bypassing the FUSE mount entirely. -use super::group_paths_by_parent; -use agentfs_sdk::{ +use super::{default_allowed_paths, group_paths_by_parent}; +use crate::opts::RunOptions; +use agentfs_core::{ AgentFS, AgentFSOptions, EncryptionConfig, HostFS, OverlayFS, PartialOriginPolicy, }; use anyhow::{bail, Context, Result}; @@ -29,27 +30,10 @@ use std::{ os::unix::fs::MetadataExt, os::unix::io::AsRawFd, path::{Path, PathBuf}, - sync::{ - atomic::{AtomicI32, AtomicU64, Ordering}, - Arc, - }, + sync::Arc, }; -/// Global child PID for signal forwarding. -/// Set by the parent before installing signal handlers. -static CHILD_PID: AtomicI32 = AtomicI32::new(0); - -/// Counter for termination signals received. -/// First signal forwards to child, second signal sends SIGKILL. -static TERM_SIGNAL_COUNT: AtomicI32 = AtomicI32::new(0); - -/// Count of pending profiling checkpoint requests (SIGUSR1). -/// Incremented in the async-signal-safe handler; drained in the wait loop where -/// it is safe to serialize and emit a profile summary. Used by the benchmark -/// harness to attribute counters to workload phases. -static PROFILE_CHECKPOINT_PENDING: AtomicU64 = AtomicU64::new(0); - -use crate::mount::{is_mountpoint, mount_fs, MountBackend, MountHandle, MountOpts}; +use agentfs_mount::{is_mountpoint, mount_fs, Backend, MountHandle, MountOpts}; /// Exit code returned when exec fails (standard shell convention for "command not found") const EXIT_COMMAND_NOT_FOUND: i32 = 127; @@ -61,147 +45,49 @@ const FUSE_MOUNT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1 /// These are skipped when remounting the filesystem hierarchy as read-only. const SKIP_MOUNT_PREFIXES: &[&str] = &["/proc", "/sys", "/dev", "/tmp"]; -/// Default directories that are allowed to be writable. -/// These are common application config/cache directories that many programs need. -const DEFAULT_ALLOWED_DIRS: &[&str] = &[ - ".amp", // Amp config - ".cache", // XDG cache directory (corepack, pip, etc.) - ".claude", // Claude Code config - ".claude.json", // Claude Code config file - ".codex", // OpenAI Codex config - ".gemini", // Gemini CLI config - ".local", // Local data directory - ".npm", // npm local registry -]; +/// World-writable temp directories hidden from the sandbox alongside the home +/// directory (sacred invariant 2: reads are scopeable). Each is replaced by an +/// empty namespace-private tmpfs, so host files there are invisible and +/// sandbox writes to them never reach the host. /dev/shm belongs here even +/// though the ro-remount pass skips everything under /dev: that skip leaves +/// the fresh scoping tmpfs writable (intended for these zones) but would leave +/// the HOST /dev/shm reachable and writable without it. +const READ_SCOPED_TMPDIRS: &[&str] = &["/tmp", "/var/tmp", "/dev/shm"]; /// Field index for mount point in /proc/self/mountinfo. /// Format: ID PARENT_ID MAJOR:MINOR ROOT MOUNT_POINT OPTIONS ... const MOUNTINFO_MOUNT_POINT_FIELD: usize = 4; -/// Signal handler that forwards signals to the child process. -/// -/// When the parent receives SIGTERM or SIGINT, this handler forwards -/// the signal to the child process so it can shut down gracefully. -/// On the second signal, SIGKILL is sent to force termination (handles -/// cases where the child ignores SIGTERM, like interactive bash). -/// -/// SAFETY: This is a signal handler. It must only use async-signal-safe functions. -/// kill() and atomic operations are async-signal-safe. -extern "C" fn forward_signal_to_child(sig: libc::c_int) { - let pid = CHILD_PID.load(Ordering::SeqCst); - if pid > 0 { - // Increment signal counter (fetch_add returns previous value) - let count = TERM_SIGNAL_COUNT.fetch_add(1, Ordering::SeqCst); - - // SAFETY: kill() is async-signal-safe - unsafe { - if count == 0 { - // First signal: forward to child gracefully - libc::kill(pid, sig); - } else { - // Second+ signal: force kill the child - libc::kill(pid, libc::SIGKILL); - } - } - } -} - -/// Signal handler that records a pending profiling checkpoint request. -/// -/// The sandboxed workload sends SIGUSR1 at phase boundaries; we only flag the -/// request here and let the wait loop emit the (async-signal-unsafe) summary. -/// -/// SAFETY: This is a signal handler. It only performs an atomic increment, which -/// is async-signal-safe. -extern "C" fn request_profile_checkpoint(_sig: libc::c_int) { - PROFILE_CHECKPOINT_PENDING.fetch_add(1, Ordering::SeqCst); -} - -/// Emit one profile checkpoint per pending SIGUSR1, draining the counter. +/// Run a command in an overlay sandbox. /// -/// Called from the wait loop in normal (non-handler) context, where serializing -/// and writing the profile summary is safe. -fn drain_profile_checkpoints() { - let pending = PROFILE_CHECKPOINT_PENDING.swap(0, Ordering::SeqCst); - for _ in 0..pending { - agentfs_sdk::profiling::report_checkpoint(); - } -} - -/// Install signal handlers to forward SIGTERM and SIGINT to the child process. +/// Forks the sandbox child BEFORE any tokio runtime exists: forking a live +/// multi-threaded runtime can deadlock the child on the allocator lock held +/// by another worker thread at fork time (cli-cmd-sandbox-deep Q4). /// -/// This ensures that when the parent receives a termination signal, it forwards -/// it to the child and waits for it to exit before cleaning up. -fn install_signal_handlers() { - // Reset the signal counter for fresh signal handling - TERM_SIGNAL_COUNT.store(0, Ordering::SeqCst); - PROFILE_CHECKPOINT_PENDING.store(0, Ordering::SeqCst); - - // SAFETY: sigaction() and sigprocmask() with valid signal numbers are safe. - // SA_RESTART ensures most syscalls restart after the handler returns. - unsafe { - // Ensure SIGTERM and SIGINT are not blocked (tokio might block them in worker threads) - let mut sigset: libc::sigset_t = std::mem::zeroed(); - libc::sigemptyset(&mut sigset); - libc::sigaddset(&mut sigset, libc::SIGTERM); - libc::sigaddset(&mut sigset, libc::SIGINT); - libc::pthread_sigmask(libc::SIG_UNBLOCK, &sigset, std::ptr::null_mut()); - - let mut sa: libc::sigaction = std::mem::zeroed(); - libc::sigemptyset(&mut sa.sa_mask); - sa.sa_sigaction = forward_signal_to_child as *const () as usize; - sa.sa_flags = libc::SA_RESTART; - - if libc::sigaction(libc::SIGTERM, &sa, std::ptr::null_mut()) != 0 { - panic!( - "failed to install SIGTERM handler: {}", - std::io::Error::last_os_error() - ); - } - if libc::sigaction(libc::SIGINT, &sa, std::ptr::null_mut()) != 0 { - panic!( - "failed to install SIGINT handler: {}", - std::io::Error::last_os_error() - ); - } - - // SIGUSR1 requests a profiling checkpoint. Install it WITHOUT SA_RESTART - // so the blocking waitpid in the wait loop returns EINTR, giving us a - // safe point to emit the (async-signal-unsafe) profile summary. - libc::sigaddset(&mut sigset, libc::SIGUSR1); - libc::pthread_sigmask(libc::SIG_UNBLOCK, &sigset, std::ptr::null_mut()); - let mut usr_sa: libc::sigaction = std::mem::zeroed(); - libc::sigemptyset(&mut usr_sa.sa_mask); - usr_sa.sa_sigaction = request_profile_checkpoint as *const () as usize; - usr_sa.sa_flags = 0; - if libc::sigaction(libc::SIGUSR1, &usr_sa, std::ptr::null_mut()) != 0 { - panic!( - "failed to install SIGUSR1 handler: {}", - std::io::Error::last_os_error() - ); - } - } -} - -/// Run a command in an overlay sandbox. -#[allow(clippy::too_many_arguments)] -pub async fn run_cmd( - allow: Vec, - no_default_allows: bool, - session_id: Option, - system: bool, - encryption: Option<(String, String)>, - partial_origin_policy: Option, - command: PathBuf, - args: Vec, -) -> Result<()> { +/// The pre-runtime fork order means the parent's FUSE mount happens after +/// the child's `unshare(CLONE_NEWNS)`, so the child sees it only through +/// shared mount propagation on `/` (systemd default; kernel default is +/// private). `run_child` guards this prerequisite with an is-mountpoint +/// check before binding the overlay over cwd. +pub fn run(options: RunOptions) -> Result<()> { + let RunOptions { + allow, + no_default_allows, + session, + system, + encryption, + partial_origin_policy, + command, + args, + } = options; let cwd = std::env::current_dir().context("Failed to get current directory")?; + let run_started_at_ms = unix_timestamp_ms(); // Build the list of allowed writable paths let allowed_paths = build_allowed_paths(&allow, no_default_allows)?; // Check if we're joining an existing session - let session = setup_run_directory(session_id)?; + let session = setup_run_directory(session)?; // If the FUSE mountpoint is already mounted, join the existing session if is_mountpoint(&session.fuse_mountpoint) { @@ -224,10 +110,192 @@ pub async fn run_cmd( print_welcome_banner(&cwd, &allowed_paths, &session.run_id, encryption.is_some()); + // SAFETY: getuid/getgid are always safe + let uid = unsafe { libc::getuid() }; + let gid = unsafe { libc::getgid() }; + + // Create pipes for parent-child coordination. + // The parent needs to write uid_map/gid_map for the child after unshare. + let (pipe_to_child, pipe_to_parent) = create_sync_pipes()?; + + // SAFETY: the process is still single-threaded here (no tokio runtime + // yet), so the child cannot inherit a lock held by another thread. + let child_pid = unsafe { libc::fork() }; + + if child_pid < 0 { + bail!("Failed to fork: {}", std::io::Error::last_os_error()); + } + + if child_pid == 0 { + // SAFETY: Closing unused pipe ends in child; these fds are valid from pipe() + unsafe { + libc::close(pipe_to_child[1]); // Close write end + libc::close(pipe_to_parent[0]); // Close read end + } + + run_child( + &cwd, + &session.fuse_mountpoint, + &allowed_paths, + command, + args, + &session.run_id, + pipe_to_child[0], + pipe_to_parent[1], + ); + } + + // SAFETY: Closing unused pipe ends in parent; these fds are valid from pipe() + unsafe { + libc::close(pipe_to_child[0]); // Close read end + libc::close(pipe_to_parent[1]); // Close write end + } + + // The child blocks on the pipe protocol until the parent signals, so the + // FUSE mount is guaranteed live before the child bind-mounts it. + let rt = crate::get_runtime(); + let audit_encryption = encryption.clone(); + let mount_handle = match rt.block_on(mount_session_fs( + &cwd, + &session, + encryption, + partial_origin_policy, + uid, + gid, + system, + )) { + Ok(handle) => handle, + Err(e) => { + eprintln!("Error: {e:#}"); + abort_child(pipe_to_child[1], child_pid); + } + }; + + // Wait for child to signal it has called unshare + if !wait_for_pipe_signal(pipe_to_parent[0]) { + eprintln!("Error: Failed to read sync signal from child process"); + abort_child_with_teardown(&rt, mount_handle, pipe_to_child[1], child_pid); + } + + // Configure user namespace mappings for the child + if let Err(error) = write_namespace_mappings(child_pid, uid, gid) { + eprintln!("Error: {error:#}"); + abort_child_with_teardown(&rt, mount_handle, pipe_to_child[1], child_pid); + } + + // Signal child that mappings are done + // SAFETY: Writing to and closing valid pipe fds + unsafe { + libc::write(pipe_to_child[1], b"x".as_ptr() as *const libc::c_void, 1); + libc::close(pipe_to_child[1]); + libc::close(pipe_to_parent[0]); + } + + // Write proc file for this session (owner = true) + if let Err(e) = + crate::cmd::ps::write_proc_file(&session.run_id, true, &command.to_string_lossy(), &cwd) + { + eprintln!("Warning: Failed to write proc file: {}", e); + } + + let exit_code = rt.block_on(run_parent(child_pid, mount_handle, &session.run_id))?; + + // The mount is torn down and the delta DB finalized, so the session's own + // audit row can be written through a plain reopen without a second opener + // racing the live FUSE session. + if let Err(error) = rt.block_on(record_run_audit( + &session.db_path, + &session.run_id, + &command, + &args, + audit_encryption, + run_started_at_ms, + exit_code, + )) { + eprintln!("Warning: Failed to record run session in timeline: {error:#}"); + } + + std::process::exit(exit_code); +} + +/// Record the completed run session in the delta database's tool-call audit +/// log so `agentfs timeline ` reports the sandboxed command and +/// its exit status (VAL-CROSS-006). +async fn record_run_audit( + db_path: &Path, + session_id: &str, + command: &Path, + args: &[String], + encryption: Option, + started_at_ms: i64, + exit_code: i32, +) -> Result<()> { + let db_path_str = db_path + .to_str() + .context("Database path contains non-UTF8 characters")?; + let mut options = AgentFSOptions::with_path(db_path_str) + .with_core_config(crate::config::core_config_from_env()); + if let Some(encryption) = encryption { + options = options.with_encryption(encryption); + } + let agentfs = AgentFS::open(options) + .await + .map_err(|err| crate::cmd::migrate::open_error_with_guidance(err, db_path_str)) + .context("Failed to reopen delta database for the audit row")?; + + let mut argv = vec![command.to_string_lossy().into_owned()]; + argv.extend(args.iter().cloned()); + let parameters = serde_json::json!({ "session": session_id, "command": argv }); + let result = serde_json::json!({ "exit_code": exit_code }); + let error_text = (exit_code != 0).then(|| format!("exit code {exit_code}")); + + agentfs + .tools + .record( + "run", + started_at_ms, + unix_timestamp_ms(), + Some(parameters), + Some(result), + error_text.as_deref(), + ) + .await + .context("Failed to insert the run audit row")?; + + // Leave the DB in single-file journal mode again: without this the audit + // insert sits in a fresh WAL sidecar, undoing the clean teardown the + // supervised session just performed. + agentfs + .fs + .finalize() + .await + .context("Failed to finalize the delta database after the audit row")?; + Ok(()) +} + +fn unix_timestamp_ms() -> i64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_millis() as i64) + .unwrap_or(0) +} + +/// Open the delta database, build the overlay, and mount it on the session's +/// FUSE mountpoint. +async fn mount_session_fs( + cwd: &Path, + session: &RunSession, + encryption: Option, + partial_origin_policy: Option, + uid: libc::uid_t, + gid: libc::gid_t, + system: bool, +) -> Result { // Open the directory BEFORE mounting FUSE on top of it. // This fd lets us access the underlying directory through /proc/self/fd/N, - // bypassing the FUSE mount that will be placed on top. - let cwd_fd = std::fs::File::open(&cwd).context("Failed to open current directory")?; + // bypassing the FUSE mount that will be placed on top. HostFS dups the fd + // at construction, so ours only needs to outlive HostFS::new. + let cwd_fd = std::fs::File::open(cwd).context("Failed to open current directory")?; let fd_num = cwd_fd.as_raw_fd(); let fd_path = format!("/proc/self/fd/{}", fd_num); @@ -235,15 +303,14 @@ pub async fn run_cmd( .db_path .to_str() .context("Database path contains non-UTF8 characters")?; - let mut options = AgentFSOptions::with_path(db_path_str); - if let Some((key, cipher)) = encryption { - options = options.with_encryption(EncryptionConfig { - hex_key: key, - cipher, - }); + let mut options = AgentFSOptions::with_path(db_path_str) + .with_core_config(crate::config::core_config_from_env()); + if let Some(encryption) = encryption { + options = options.with_encryption(encryption); } let agentfs = AgentFS::open(options) .await + .map_err(|err| crate::cmd::migrate::open_error_with_guidance(err, db_path_str)) .context("Failed to create delta AgentFS")?; let hostfs = HostFS::new(&fd_path).context("Failed to create HostFS")?; @@ -274,13 +341,9 @@ pub async fn run_cmd( std::fs::write(&session.base_path_file, cwd_str) .context("Failed to write session base path")?; - // SAFETY: getuid/getgid are always safe - let uid = unsafe { libc::getuid() }; - let gid = unsafe { libc::getgid() }; - let mount_opts = MountOpts { mountpoint: session.fuse_mountpoint.clone(), - backend: MountBackend::Fuse, + backend: Backend::Fuse, fsname: format!("agentfs:{}", session.run_id), uid: Some(uid), gid: Some(gid), @@ -291,75 +354,7 @@ pub async fn run_cmd( timeout: FUSE_MOUNT_TIMEOUT, }; - // Mount the overlay filesystem - let mount_handle = mount_fs(Arc::new(overlay), mount_opts).await?; - - // Create pipes for parent-child coordination. - // The parent needs to write uid_map/gid_map for the child after unshare. - let (pipe_to_child, pipe_to_parent) = create_sync_pipes()?; - - // SAFETY: fork() is safe when called from a single-threaded context before - // the child performs any async-signal-unsafe operations. Our child immediately - // closes unused fds and calls exec after namespace setup. - let child_pid = unsafe { libc::fork() }; - - if child_pid < 0 { - bail!("Failed to fork: {}", std::io::Error::last_os_error()); - } - - if child_pid == 0 { - // SAFETY: Closing unused pipe ends in child; these fds are valid from pipe() - unsafe { - libc::close(pipe_to_child[1]); // Close write end - libc::close(pipe_to_parent[0]); // Close read end - } - - // Close the fd in child - we don't need it (parent keeps it for FUSE) - drop(cwd_fd); - run_child( - &cwd, - &session.fuse_mountpoint, - &allowed_paths, - command, - args, - &session.run_id, - pipe_to_child[0], - pipe_to_parent[1], - ); - } else { - // SAFETY: Closing unused pipe ends in parent; these fds are valid from pipe() - unsafe { - libc::close(pipe_to_child[0]); // Close read end - libc::close(pipe_to_parent[1]); // Close write end - } - - // Wait for child to signal it has called unshare - if !wait_for_pipe_signal(pipe_to_parent[0]) { - eprintln!("Error: Failed to read sync signal from child process"); - abort_child(pipe_to_child[1], child_pid); - } - - // Configure user namespace mappings for the child - write_namespace_mappings(child_pid, uid, gid, pipe_to_child[1]); - - // Signal child that mappings are done - // SAFETY: Writing to and closing valid pipe fds - unsafe { - libc::write(pipe_to_child[1], b"x".as_ptr() as *const libc::c_void, 1); - libc::close(pipe_to_child[1]); - libc::close(pipe_to_parent[0]); - } - - // Write proc file for this session (owner = true) - if let Err(e) = - crate::cmd::ps::write_proc_file(&session.run_id, true, &command.to_string_lossy(), &cwd) - { - eprintln!("Warning: Failed to write proc file: {}", e); - } - - // Keep cwd_fd alive - it's needed by HostFS in the FUSE thread - run_parent(child_pid, cwd_fd, mount_handle, &session.run_id); - } + mount_fs(Arc::new(overlay), mount_opts).await } /// Run a command in an existing session's FUSE mount. @@ -381,7 +376,8 @@ fn run_in_existing_session( // Create pipes for parent-child coordination. let (pipe_to_child, pipe_to_parent) = create_sync_pipes()?; - // SAFETY: fork() is safe here + // SAFETY: the process is still single-threaded here (no tokio runtime + // yet), so the child cannot inherit a lock held by another thread. let child_pid = unsafe { libc::fork() }; if child_pid < 0 { @@ -405,51 +401,56 @@ fn run_in_existing_session( pipe_to_child[0], pipe_to_parent[1], ); - } else { - // Parent process - unsafe { - libc::close(pipe_to_child[0]); - libc::close(pipe_to_parent[1]); - } + } - // Wait for child to signal it has called unshare - if !wait_for_pipe_signal(pipe_to_parent[0]) { - eprintln!("Error: Failed to read sync signal from child process"); - abort_child(pipe_to_child[1], child_pid); - } + // Parent process + unsafe { + libc::close(pipe_to_child[0]); + libc::close(pipe_to_parent[1]); + } - // Configure user namespace mappings for the child - write_namespace_mappings(child_pid, uid, gid, pipe_to_child[1]); + // Wait for child to signal it has called unshare + if !wait_for_pipe_signal(pipe_to_parent[0]) { + eprintln!("Error: Failed to read sync signal from child process"); + abort_child(pipe_to_child[1], child_pid); + } - // Signal child that mappings are done - unsafe { - libc::write(pipe_to_child[1], b"x".as_ptr() as *const libc::c_void, 1); - libc::close(pipe_to_child[1]); - libc::close(pipe_to_parent[0]); - } + // Configure user namespace mappings for the child + if let Err(error) = write_namespace_mappings(child_pid, uid, gid) { + eprintln!("Error: {error:#}"); + abort_child(pipe_to_child[1], child_pid); + } - // Write proc file for this joined session (owner = false) - if let Err(e) = - crate::cmd::ps::write_proc_file(session_id, false, &command.to_string_lossy(), cwd) - { - eprintln!("Warning: Failed to write proc file: {}", e); - } + // Signal child that mappings are done + unsafe { + libc::write(pipe_to_child[1], b"x".as_ptr() as *const libc::c_void, 1); + libc::close(pipe_to_child[1]); + libc::close(pipe_to_parent[0]); + } - // Store child PID and install signal handlers before waiting - CHILD_PID.store(child_pid, Ordering::SeqCst); - install_signal_handlers(); + // Write proc file for this joined session (owner = false) + if let Err(e) = + crate::cmd::ps::write_proc_file(session_id, false, &command.to_string_lossy(), cwd) + { + eprintln!("Warning: Failed to write proc file: {}", e); + } - // Wait for child to exit (don't unmount or cleanup - the original session owns that) - // Retry on EINTR (signal interruption) - let exit_code = wait_for_child(child_pid); + let rt = crate::get_runtime(); + let status = rt.block_on(agentfs_mount::supervise::supervise_pid_with_hooks( + child_pid, + agentfs_mount::supervise::SuperviseOpts::default(), + agentfs_mount::supervise::SuperviseHooks::with_profile_checkpoint( + crate::profiling::report_checkpoint, + ), + ))?; + let exit_code = agentfs_mount::supervise::exit_code_for_status(status); - // Clean up proc file - crate::cmd::ps::remove_proc_file(session_id); + // Clean up proc file + crate::cmd::ps::remove_proc_file(session_id); - agentfs_sdk::profiling::report_summary("run_parent"); + crate::profiling::emit_cli_report(); - std::process::exit(exit_code); - } + std::process::exit(exit_code); } /// Print the welcome banner showing sandbox configuration. @@ -463,7 +464,8 @@ fn print_welcome_banner(cwd: &Path, allowed_paths: &[PathBuf], session_id: &str, eprintln!(" - {}", grouped_path); } eprintln!(); - eprintln!("🔒 Everything else is read-only."); + eprintln!("🔒 System paths are read-only."); + eprintln!("🙈 Other files under your home directory and temp dirs (/tmp, /var/tmp, /dev/shm) are hidden."); if encrypted { eprintln!("🔐 Delta layer is encrypted."); } @@ -545,48 +547,63 @@ fn wait_for_pipe_signal(fd: libc::c_int) -> bool { /// /// Closes the pipe to signal the child, waits for it to exit, then exits. fn abort_child(pipe_write_fd: libc::c_int, child_pid: libc::pid_t) -> ! { + reap_child(pipe_write_fd, child_pid); + std::process::exit(1) +} + +/// Abort variant for parent errors raised while the session's FUSE mount is +/// already live: `process::exit` runs no destructors, so without the explicit +/// unmount the mount would only be torn down by process death, leaving a dead +/// mount table entry behind. Reap the child first (mirroring run_parent's +/// supervise-then-teardown order), then run the bounded graceful teardown. +fn abort_child_with_teardown( + rt: &tokio::runtime::Runtime, + mount_handle: MountHandle, + pipe_write_fd: libc::c_int, + child_pid: libc::pid_t, +) -> ! { + reap_child(pipe_write_fd, child_pid); + if let Err(error) = rt.block_on(mount_handle.unmount()) { + eprintln!("Warning: Failed to tear down the FUSE session during abort: {error:#}"); + } + std::process::exit(1) +} + +/// Close the coordination pipe to signal the child, then wait for it to exit. +fn reap_child(pipe_write_fd: libc::c_int, child_pid: libc::pid_t) { // SAFETY: Closing valid fd and waiting for valid child pid unsafe { libc::close(pipe_write_fd); let mut status: libc::c_int = 0; libc::waitpid(child_pid, &mut status, 0); } - std::process::exit(1) } /// Write uid_map, gid_map, and setgroups for a child's user namespace. /// /// Maps the real uid/gid to itself inside the namespace, so the user appears /// as themselves (not root) inside the sandbox. -/// On failure, aborts the child and exits. fn write_namespace_mappings( child_pid: libc::pid_t, uid: libc::uid_t, gid: libc::gid_t, - pipe_write_fd: libc::c_int, -) { +) -> Result<()> { let uid_map_path = format!("/proc/{}/uid_map", child_pid); let gid_map_path = format!("/proc/{}/gid_map", child_pid); let setgroups_path = format!("/proc/{}/setgroups", child_pid); // Map the user's UID to itself (inside_uid outside_uid count) - if let Err(e) = std::fs::write(&uid_map_path, format!("{} {} 1\n", uid, uid)) { - eprintln!("Error: Could not write uid_map: {}", e); - eprintln!("This may indicate missing unprivileged user namespace support."); - abort_child(pipe_write_fd, child_pid); - } + std::fs::write(&uid_map_path, format!("{} {} 1\n", uid, uid)).context( + "Could not write uid_map (this may indicate missing unprivileged user namespace support)", + )?; // Disable setgroups (required before writing gid_map on unprivileged systems) - if let Err(e) = std::fs::write(&setgroups_path, "deny") { - eprintln!("Error: Could not write setgroups: {}", e); - abort_child(pipe_write_fd, child_pid); - } + std::fs::write(&setgroups_path, "deny").context("Could not write setgroups")?; // Map the user's GID to itself (inside_gid outside_gid count) - if let Err(e) = std::fs::write(&gid_map_path, format!("{} {} 1\n", gid, gid)) { - eprintln!("Error: Could not write gid_map: {}", e); - abort_child(pipe_write_fd, child_pid); - } + std::fs::write(&gid_map_path, format!("{} {} 1\n", gid, gid)) + .context("Could not write gid_map")?; + Ok(()) } /// Convert a path to a CString, exiting the child process on failure. @@ -635,6 +652,10 @@ fn run_child( pipe_from_parent: libc::c_int, pipe_to_parent: libc::c_int, ) -> ! { + if let Err(error) = agentfs_mount::supervise::prepare_forked_child(true) { + child_exit(&format!("Failed to prepare supervised child: {error}")); + } + // Step 1: Create new user + mount namespaces for unprivileged isolation. // User namespace gives us CAP_SYS_ADMIN within the namespace to manipulate mounts. // SAFETY: unshare() with valid flags is safe; we handle the error case. @@ -678,6 +699,24 @@ fn run_child( )); } + // Step 5 prerequisite: the parent mounts FUSE AFTER this child unshared + // its mount namespace, so the mount reaches this namespace only via + // shared mount propagation from the parent (systemd makes `/` rshared; + // the kernel default is private). The maps-done signal above is sent + // post-mount, so a missing mount here is a propagation failure, not a + // race. Without this guard the bind below would silently place the + // EMPTY underlying directory over cwd. + if !is_mountpoint(fuse_mountpoint) { + child_exit(&format!( + "FUSE overlay never appeared at {} inside the sandbox mount namespace.\n\ + agentfs run requires shared mount propagation on / (the systemd default);\n\ + this host appears to use private propagation (check with\n\ + `findmnt -o TARGET,PROPAGATION /`). Enable it with `mount --make-rshared /`\n\ + and retry.", + fuse_mountpoint.display() + )); + } + // Step 5: Bind mount the FUSE overlay from temp dir onto cwd. // This is only visible in this namespace, not to other processes. let fuse_cstr = path_to_cstring(fuse_mountpoint, "FUSE mountpoint path"); @@ -705,15 +744,187 @@ fn run_child( child_exit("Failed to change to working directory"); } - // Step 7: Remount all other filesystems as read-only. + // Step 7: Hide scoped host data (home and temp dirs) behind fresh tmpfs, + // re-exposing only the overlay cwd and the allowed paths. + if let Err(e) = scope_reads(cwd, allowed_paths) { + child_exit(&format!("Failed to scope sandbox reads: {:#}", e)); + } + + // Step 8: Remount all other filesystems as read-only. if let Err(e) = remount_all_readonly_except(cwd, allowed_paths) { child_exit(&format!("Failed to remount filesystems read-only: {}", e)); } - // Step 8: Execute the command (does not return). + // Step 9: Execute the command (does not return). exec_command(command, args, session_id); } +/// A read-scoping zone to hide behind a tmpfs, plus the paths inside it that +/// must stay visible. +#[derive(Debug, PartialEq, Eq)] +pub(super) struct ZonePlan { + /// Zone root the tmpfs is mounted on. + pub(super) root: PathBuf, + /// Whether the tmpfs is world-writable (temp dirs) or user-private (home). + pub(super) world_writable: bool, + /// Paths re-bound into the tmpfs, parents before children. + pub(super) keep: Vec, +} + +/// Decide which zones to hide and which paths to re-expose inside each. +/// +/// All inputs must already be canonicalized. A zone that is itself covered by +/// the overlay cwd is skipped (the overlay already owns those reads), and +/// allowed paths inside the cwd are skipped for the same reason. +pub(super) fn plan_read_scoping( + zones: &[(PathBuf, bool)], + cwd: &Path, + allowed_paths: &[PathBuf], +) -> Vec { + zones + .iter() + .filter_map(|(zone, world_writable)| { + if zone.starts_with(cwd) { + return None; + } + let mut keep: Vec = allowed_paths + .iter() + .filter(|path| path.starts_with(zone) && !path.starts_with(cwd)) + .cloned() + .collect(); + if cwd.starts_with(zone) { + keep.push(cwd.to_path_buf()); + } + keep.sort_by_key(|path| path.components().count()); + Some(ZonePlan { + root: zone.clone(), + world_writable: *world_writable, + keep, + }) + }) + .collect() +} + +/// Hide the home directory and temp dirs behind namespace-private tmpfs, +/// keeping only the overlay cwd and allowed paths visible. +/// +/// Runs in the child's mount namespace after uid/gid maps are written (tmpfs +/// mounts need CAP_SYS_ADMIN in the user namespace) and before the read-only +/// remount pass, so the fresh tmpfs zones are part of that pass's mount scan. +fn scope_reads(cwd: &Path, allowed_paths: &[PathBuf]) -> Result<()> { + let cwd = cwd.canonicalize().unwrap_or_else(|_| cwd.to_path_buf()); + + let mut zones: Vec<(PathBuf, bool)> = Vec::new(); + if let Some(home) = dirs::home_dir().and_then(|home| home.canonicalize().ok()) { + if home != Path::new("/") { + zones.push((home, false)); + } + } + for dir in READ_SCOPED_TMPDIRS { + if let Ok(zone) = Path::new(dir).canonicalize() { + zones.push((zone, true)); + } + } + + let allowed: Vec = allowed_paths + .iter() + .filter_map(|path| path.canonicalize().ok()) + .collect(); + + for plan in plan_read_scoping(&zones, &cwd, &allowed) { + apply_zone_plan(&plan)?; + } + Ok(()) +} + +/// Mount a tmpfs over the zone root, then re-bind each kept path into it via +/// a pre-opened fd (the tmpfs shadows the original host paths). +fn apply_zone_plan(plan: &ZonePlan) -> Result<()> { + let mut keeps: Vec<(PathBuf, libc::c_int, bool)> = Vec::new(); + for path in &plan.keep { + let is_dir = std::fs::metadata(path) + .with_context(|| format!("Failed to stat kept path {}", path.display()))? + .is_dir(); + let path_cstr = path_to_cstring(path, "kept path"); + // SAFETY: opening a valid NUL-terminated path with O_PATH + let fd = unsafe { libc::open(path_cstr.as_ptr(), libc::O_PATH | libc::O_CLOEXEC) }; + if fd < 0 { + bail!( + "Failed to open kept path {}: {}", + path.display(), + std::io::Error::last_os_error() + ); + } + keeps.push((path.clone(), fd, is_dir)); + } + + // getuid/getgid return the mapped ids here (uid_map is already written). + // SAFETY: getuid/getgid are always safe + let (uid, gid) = unsafe { (libc::getuid(), libc::getgid()) }; + let data = if plan.world_writable { + "mode=1777".to_string() + } else { + format!("mode=0700,uid={uid},gid={gid}") + }; + let data_cstr = CString::new(data).expect("tmpfs options contain no NUL bytes"); + let fstype = CString::new("tmpfs").expect("static string"); + let root_cstr = path_to_cstring(&plan.root, "scoped zone root"); + // SAFETY: mounting a fresh tmpfs over a valid path in our own namespace + if unsafe { + libc::mount( + fstype.as_ptr(), + root_cstr.as_ptr(), + fstype.as_ptr(), + libc::MS_NOSUID | libc::MS_NODEV, + data_cstr.as_ptr() as *const libc::c_void, + ) + } != 0 + { + bail!( + "Failed to mount scoping tmpfs on {}: {}", + plan.root.display(), + std::io::Error::last_os_error() + ); + } + + for (path, fd, is_dir) in keeps { + if is_dir { + std::fs::create_dir_all(&path) + } else { + match path.parent() { + Some(parent) => std::fs::create_dir_all(parent), + None => Ok(()), + } + .and_then(|()| std::fs::File::create(&path).map(drop)) + } + .with_context(|| format!("Failed to recreate {} in tmpfs", path.display()))?; + + let src = CString::new(format!("/proc/self/fd/{fd}")).expect("fd path has no NUL bytes"); + let dst = path_to_cstring(&path, "kept path"); + // SAFETY: bind-mounting the pre-opened host path onto its tmpfs shadow + if unsafe { + libc::mount( + src.as_ptr(), + dst.as_ptr(), + std::ptr::null(), + libc::MS_BIND, + std::ptr::null(), + ) + } != 0 + { + bail!( + "Failed to re-bind {} into scoped zone: {}", + path.display(), + std::io::Error::last_os_error() + ); + } + // SAFETY: closing an fd we opened above + unsafe { libc::close(fd) }; + } + + Ok(()) +} + /// Remount all filesystems as read-only, except for the specified paths. /// /// The correct sequence to keep allowed paths writable: @@ -855,12 +1066,12 @@ fn remount_all_readonly_except( /// Check if a mount point should be skipped during read-only remounting. /// /// Virtual filesystems like /proc, /sys, and /dev must remain writable -/// for the system to function correctly. -fn skip_mount(path: &Path) -> bool { - let path_str = path.to_string_lossy(); +/// for the system to function correctly. Compared per path component so a +/// sibling like `/devfoo` is still remounted read-only. +pub(super) fn skip_mount(path: &Path) -> bool { SKIP_MOUNT_PREFIXES .iter() - .any(|prefix| path_str.starts_with(prefix)) + .any(|prefix| path.starts_with(prefix)) } /// Build the list of allowed writable paths from user input and defaults. @@ -870,13 +1081,7 @@ fn build_allowed_paths(user_allowed: &[PathBuf], no_default_allows: bool) -> Res // Add default allowed directories unless disabled if !no_default_allows { if let Some(home) = dirs::home_dir() { - for dir in DEFAULT_ALLOWED_DIRS { - let path = home.join(dir); - // Only add if the path exists - if path.exists() { - allowed.push(path); - } - } + allowed.extend(default_allowed_paths(&home)); } } @@ -935,33 +1140,22 @@ fn unescape_mountinfo(s: &str) -> String { } /// Parent process: wait for child to exit, then clean up. -/// -/// The MountHandle automatically unmounts when dropped. We explicitly drop it -/// before calling exit() to ensure cleanup happens. -fn run_parent( - child_pid: i32, - cwd_fd: std::fs::File, - mount_handle: MountHandle, - session_id: &str, -) -> ! { - // Store child PID and install signal handlers before waiting - CHILD_PID.store(child_pid, Ordering::SeqCst); - install_signal_handlers(); - - // Wait for child process to exit, retrying on EINTR (signal interruption) - let exit_code = wait_for_child(child_pid); - - // Clean up proc file - crate::cmd::ps::remove_proc_file(session_id); - +async fn run_parent(child_pid: i32, mount_handle: MountHandle, session_id: &str) -> Result { // Get mountpoint before dropping handle let fuse_mountpoint = mount_handle.mountpoint().to_path_buf(); - // Release the underlying directory fd (was kept alive for HostFS) - drop(cwd_fd); + let status = agentfs_mount::supervise::run_supervised_pid_with_hooks( + mount_handle, + child_pid, + agentfs_mount::supervise::SuperviseOpts::default(), + agentfs_mount::supervise::SuperviseHooks::with_profile_checkpoint( + crate::profiling::report_checkpoint, + ), + ) + .await; - // Drop the mount handle to unmount (this also moves away from mountpoint) - drop(mount_handle); + // Clean up proc file + crate::cmd::ps::remove_proc_file(session_id); // Clean up the FUSE mountpoint directory (but keep the delta database) if let Err(e) = std::fs::remove_dir_all(&fuse_mountpoint) { @@ -986,9 +1180,9 @@ fn run_parent( eprintln!("To see what changed:"); eprintln!(" agentfs diff {}", session_id); - agentfs_sdk::profiling::report_summary("run_parent"); + crate::profiling::emit_cli_report(); - std::process::exit(exit_code); + Ok(agentfs_mount::supervise::exit_code_for_status(status?)) } /// Execute the command, replacing the current process. @@ -1028,18 +1222,20 @@ fn exec_command(command: PathBuf, args: Vec, session_id: &str) -> ! { libc::execvp(cmd_cstr.as_ptr(), argv_ptrs.as_ptr()); } + let error = std::io::Error::last_os_error(); child_exit_with_code( - &format!( - "Failed to execute {}: {}", - command.display(), - std::io::Error::last_os_error() - ), - EXIT_COMMAND_NOT_FOUND, + &format!("Error: Failed to execute {}: {}", command.display(), error), + agentfs_mount::supervise::exit_code_for_spawn_error(&error) + .unwrap_or(EXIT_COMMAND_NOT_FOUND), ); } /// Setup environment variables for the sandbox. fn setup_env_vars(session_id: &str) { + // The parent CLI overrode TMPDIR with its private spill dir; the + // sandboxed command must see the user's TMPDIR instead (inside the + // namespace /tmp is a fresh scoping tmpfs anyway). + crate::config::restore_original_tmpdir_env(); std::env::set_var("AGENTFS", "1"); std::env::set_var("AGENTFS_SANDBOX", "linux-namespace"); std::env::set_var("AGENTFS_SESSION", session_id); @@ -1060,39 +1256,3 @@ fn setup_env_vars(session_id: &str) { std::env::set_var("GIT_SSH_COMMAND", format!("ssh -F {}", config_path)); } } - -/// Wait for a child process to exit, retrying on EINTR. -/// -/// Returns the exit code of the child process, or 1 if waitpid fails. -fn wait_for_child(child_pid: libc::pid_t) -> i32 { - let mut status: libc::c_int = 0; - loop { - // SAFETY: waitpid with valid child pid is safe - let result = unsafe { libc::waitpid(child_pid, &mut status, 0) }; - if result == -1 { - let err = std::io::Error::last_os_error(); - if err.raw_os_error() == Some(libc::EINTR) { - // Interrupted by signal. Emit any pending profile checkpoints - // (SIGUSR1) here, in a context where it is safe to do so. - drain_profile_checkpoints(); - // Interrupted by signal, retry - continue; - } - // Other error, return exit code 1 - return 1; - } - break; - } - wait_status_to_exit_code(status) -} - -/// Extract exit code from wait status. -fn wait_status_to_exit_code(status: libc::c_int) -> i32 { - if libc::WIFEXITED(status) { - libc::WEXITSTATUS(status) - } else if libc::WIFSIGNALED(status) { - 128 + libc::WTERMSIG(status) - } else { - 1 - } -} diff --git a/crates/agentfs-cli/src/cmd/run/mod.rs b/crates/agentfs-cli/src/cmd/run/mod.rs new file mode 100644 index 00000000..7391ad8a --- /dev/null +++ b/crates/agentfs-cli/src/cmd/run/mod.rs @@ -0,0 +1,112 @@ +//! Run command - common entry point. +//! +//! Dispatches to platform-specific implementations: +//! - Linux: FUSE + namespace sandbox +//! - Darwin: NFS + sandbox-exec + +use crate::opts::RunOptions; +use anyhow::Result; +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +#[cfg(target_os = "macos")] +mod darwin; +#[cfg(target_os = "linux")] +mod linux; +#[cfg(not(any(target_os = "linux", target_os = "macos")))] +mod not_supported; + +#[cfg(test)] +mod tests; + +#[cfg(target_os = "macos")] +use darwin as sys; +#[cfg(target_os = "linux")] +use linux as sys; +#[cfg(not(any(target_os = "linux", target_os = "macos")))] +use not_supported as sys; + +/// Default directories in HOME granted read/write access in the sandbox. +/// +/// Common agent/tool config and cache directories that programs need at +/// runtime. One list for every platform: the per-platform copies diverged +/// silently (Linux lacked `.config`/`.bun`, macOS lacked `.codex`), so this +/// is deliberately the superset. +const DEFAULT_ALLOWED_DIRS: &[&str] = &[ + ".amp", // Amp config + ".bun", // Used by opencode to install packages at runtime + ".cache", // XDG cache directory (corepack, pip, etc.) + ".claude", // Claude Code config + ".claude.json", // Claude Code config file + ".codex", // OpenAI Codex config + ".config", // XDG config directory + ".gemini", // Gemini CLI config + ".local", // Local data directory + ".npm", // npm local registry +]; + +/// Expand `DEFAULT_ALLOWED_DIRS` against a home directory, keeping only +/// entries that exist. +fn default_allowed_paths(home: &Path) -> Vec { + DEFAULT_ALLOWED_DIRS + .iter() + .map(|dir| home.join(dir)) + .filter(|path| path.exists()) + .collect() +} + +/// Handle the `run` command, dispatching to the platform-specific implementation. +/// +/// Deliberately synchronous: the Linux backend must fork before any tokio +/// runtime exists (forking a live multi-threaded runtime can deadlock the +/// child on the allocator lock), so each backend owns its runtime. +#[cfg(target_os = "linux")] +pub fn handle_run_command(options: RunOptions) -> Result<()> { + sys::run(options) +} + +/// Handle the `run` command, dispatching to the platform-specific implementation. +#[cfg(not(target_os = "linux"))] +pub fn handle_run_command(options: RunOptions) -> Result<()> { + crate::get_runtime().block_on(sys::run(options)) +} + +/// Group paths by parent directory and format using brace expansion. +/// +/// For example, given paths: +/// - /home/user/.claude +/// - /home/user/.claude.json +/// - /home/user/.codex +/// - /home/user/.npm +/// +/// Returns: `["/home/user/{.claude, .claude.json, .codex, .npm}"]` +fn group_paths_by_parent(paths: &[PathBuf]) -> Vec { + let mut groups: BTreeMap> = BTreeMap::new(); + + for path in paths { + let (parent, name) = match (path.parent(), path.file_name()) { + (Some(parent), Some(name)) => { + (parent.to_path_buf(), name.to_string_lossy().to_string()) + } + _ => (PathBuf::new(), path.display().to_string()), + }; + groups.entry(parent).or_default().push(name); + } + + groups + .into_iter() + .map(|(parent, mut names)| { + names.sort(); + let parent_str = parent.display().to_string(); + if names.len() == 1 { + if parent_str.is_empty() { + names.remove(0) + } else { + format!("{}/{}", parent_str, names[0]) + } + } else { + format!("{}/{{{}}}", parent_str, names.join(", ")) + } + }) + .collect() +} diff --git a/crates/agentfs-cli/src/cmd/run/not_supported.rs b/crates/agentfs-cli/src/cmd/run/not_supported.rs new file mode 100644 index 00000000..b72af0bb --- /dev/null +++ b/crates/agentfs-cli/src/cmd/run/not_supported.rs @@ -0,0 +1,11 @@ +//! Unsupported-platform run command implementation. +//! +//! The `run` command is supported on Linux and macOS. + +use crate::opts::RunOptions; +use anyhow::{bail, Result}; + +/// Report that the run command is unavailable on this platform. +pub async fn run(_options: RunOptions) -> Result<()> { + bail!("The `run` command is supported only on Linux and macOS") +} diff --git a/crates/agentfs-cli/src/cmd/run/tests.rs b/crates/agentfs-cli/src/cmd/run/tests.rs new file mode 100644 index 00000000..ea8c7fa0 --- /dev/null +++ b/crates/agentfs-cli/src/cmd/run/tests.rs @@ -0,0 +1,496 @@ +use super::*; + +#[test] +fn default_allowed_dirs_is_the_cross_platform_superset() { + // The per-platform lists diverged one agent-tool fix at a time; pin the + // unified superset so a new entry lands everywhere at once. + let expected = [ + ".amp", + ".bun", + ".cache", + ".claude", + ".claude.json", + ".codex", + ".gemini", + ".config", + ".local", + ".npm", + ]; + for entry in expected { + assert!( + DEFAULT_ALLOWED_DIRS.contains(&entry), + "DEFAULT_ALLOWED_DIRS lost {entry}" + ); + } + assert_eq!(DEFAULT_ALLOWED_DIRS.len(), expected.len()); + + let mut sorted = DEFAULT_ALLOWED_DIRS.to_vec(); + sorted.sort_unstable(); + assert_eq!( + DEFAULT_ALLOWED_DIRS, + sorted.as_slice(), + "keep the list sorted" + ); +} + +#[test] +fn default_allowed_paths_keeps_only_existing_entries() { + let home = tempfile::tempdir().expect("tempdir"); + std::fs::create_dir(home.path().join(".cache")).unwrap(); + std::fs::create_dir(home.path().join(".codex")).unwrap(); + std::fs::write(home.path().join(".claude.json"), b"{}").unwrap(); + + let paths = default_allowed_paths(home.path()); + + assert_eq!( + paths, + vec![ + home.path().join(".cache"), + home.path().join(".claude.json"), + home.path().join(".codex"), + ] + ); +} + +#[cfg(target_os = "linux")] +mod read_scoping { + use super::super::linux::{plan_read_scoping, ZonePlan}; + use std::path::PathBuf; + + fn zones() -> Vec<(PathBuf, bool)> { + vec![ + (PathBuf::from("/home/user"), false), + (PathBuf::from("/tmp"), true), + ] + } + + #[test] + fn keeps_cwd_and_in_zone_allows_only() { + let cwd = PathBuf::from("/home/user/project"); + let allowed = vec![ + PathBuf::from("/home/user/.claude"), + PathBuf::from("/opt/tool"), + PathBuf::from("/tmp/scratch"), + ]; + + let plans = plan_read_scoping(&zones(), &cwd, &allowed); + + assert_eq!( + plans, + vec![ + ZonePlan { + root: PathBuf::from("/home/user"), + world_writable: false, + keep: vec![ + PathBuf::from("/home/user/.claude"), + PathBuf::from("/home/user/project"), + ], + }, + ZonePlan { + root: PathBuf::from("/tmp"), + world_writable: true, + keep: vec![PathBuf::from("/tmp/scratch")], + }, + ] + ); + } + + #[test] + fn allowed_paths_inside_cwd_are_covered_by_the_overlay() { + let cwd = PathBuf::from("/home/user/project"); + let allowed = vec![PathBuf::from("/home/user/project/.cache")]; + + let plans = plan_read_scoping(&zones(), &cwd, &allowed); + + assert_eq!(plans[0].keep, vec![PathBuf::from("/home/user/project")]); + } + + #[test] + fn zone_covered_by_cwd_is_skipped() { + let cwd = PathBuf::from("/home/user"); + let allowed = vec![PathBuf::from("/home/user/.claude")]; + + let plans = plan_read_scoping(&zones(), &cwd, &allowed); + + assert_eq!(plans.len(), 1); + assert_eq!(plans[0].root, PathBuf::from("/tmp")); + } + + #[test] + fn keep_orders_parents_before_children() { + let cwd = PathBuf::from("/srv/work"); + let allowed = vec![ + PathBuf::from("/home/user/.local/share/tool"), + PathBuf::from("/home/user/.local"), + ]; + + let plans = plan_read_scoping(&zones(), &cwd, &allowed); + + assert_eq!( + plans[0].keep, + vec![ + PathBuf::from("/home/user/.local"), + PathBuf::from("/home/user/.local/share/tool"), + ] + ); + } + + #[test] + fn sibling_lookalike_prefixes_are_not_treated_as_children() { + let cwd = PathBuf::from("/home/user/project"); + let allowed = vec![PathBuf::from("/home/user/project-notes")]; + + let plans = plan_read_scoping(&zones(), &cwd, &allowed); + + assert_eq!( + plans[0].keep, + vec![ + PathBuf::from("/home/user/project-notes"), + PathBuf::from("/home/user/project"), + ] + ); + } +} + +#[cfg(target_os = "macos")] +mod darwin_read_scoping { + use super::super::darwin::{ + generate_sandbox_profile, SandboxConfig, SandboxProfile, PLATFORM_READ_ROOTS, + }; + use std::path::{Path, PathBuf}; + + fn config() -> SandboxConfig { + SandboxConfig { + mountpoint: PathBuf::from("/Users/tester/.agentfs/run/sess-1/mnt"), + allow_paths: vec![ + PathBuf::from("/Users/tester/.codex"), + PathBuf::from("/Users/tester/.claude.json"), + ], + allow_network: true, + session_id: "sess-1".to_string(), + } + } + + fn param_for<'a>(profile: &'a SandboxProfile, path: &str) -> &'a str { + profile + .params + .iter() + .find(|(_, value)| value == Path::new(path)) + .map(|(name, _)| name.as_str()) + .unwrap_or_else(|| panic!("no -D param defined for {path}")) + } + + #[test] + fn reads_are_default_deny_with_no_blanket_allow() { + let profile = generate_sandbox_profile(&config()); + + assert!( + profile + .policy + .lines() + .any(|line| line.starts_with("(deny default")), + "profile must keep the deny-default posture" + ); + assert!( + profile + .policy + .lines() + .all(|line| line.trim() != "(allow file-read*)"), + "a bare (allow file-read*) reopens unscoped reads" + ); + } + + #[test] + fn platform_read_roots_are_all_present() { + let profile = generate_sandbox_profile(&config()); + + for root in PLATFORM_READ_ROOTS { + let rule = format!( + r#"(allow file-read* file-map-executable file-test-existence (subpath "{root}"))"# + ); + assert!( + profile.policy.contains(&rule), + "missing platform read root {root}" + ); + } + assert!( + profile + .policy + .contains(r#"(allow file-read* file-test-existence (literal "/"))"#), + "getcwd needs a metadata-capable read of the root directory" + ); + assert!( + profile.policy.contains( + r#"(require-all (subpath "/System") (require-not (subpath "/System/Volumes")))"# + ), + "/System must exclude the /System/Volumes firmlinks back into the data volume" + ); + } + + #[test] + fn session_and_allow_paths_expand_from_config_as_params() { + let profile = generate_sandbox_profile(&config()); + + for path in [ + "/Users/tester/.agentfs/run/sess-1/mnt", + "/Users/tester/.agentfs/run/sess-1", + "/Users/tester/.codex", + "/Users/tester/.claude.json", + ] { + let name = param_for(&profile, path); + let rule = format!( + r#"(allow file-read* file-map-executable file-test-existence (subpath (param "{name}")))"# + ); + assert!( + profile.policy.contains(&rule), + "missing read allow for {path}" + ); + } + } + + #[test] + fn path_resolution_parents_get_metadata_only() { + let profile = generate_sandbox_profile(&config()); + + for parent in [ + "/Users", + "/Users/tester", + "/Users/tester/.agentfs", + "/Users/tester/.agentfs/run", + "/Users/tester/.agentfs/run/sess-1", + ] { + let name = param_for(&profile, parent); + let rule = format!( + r#"(allow file-read-metadata file-test-existence (literal (param "{name}")))"# + ); + assert!( + profile.policy.contains(&rule), + "missing metadata parent {parent}" + ); + } + let home_param = param_for(&profile, "/Users/tester"); + assert!( + !profile + .policy + .contains(&format!(r#"(subpath (param "{home_param}"))"#)), + "home outside the session/allow paths must stay data-unreadable" + ); + for link in ["/etc", "/tmp", "/var", "/System/Volumes/Data"] { + let rule = + format!(r#"(allow file-read-metadata file-test-existence (literal "{link}"))"#); + assert!( + profile.policy.contains(&rule), + "missing symlink metadata for {link}" + ); + } + } + + #[test] + fn dyld_cryptex_chain_has_metadata_ancestors() { + let profile = generate_sandbox_profile(&config()); + + assert!( + profile.policy.contains( + r#"(allow file-read* file-map-executable file-test-existence (subpath "/System/Volumes/Preboot/Cryptexes"))"# + ), + "dyld shared cache cryptex must stay a data read root" + ); + for ancestor in ["/System/Volumes", "/System/Volumes/Preboot"] { + let rule = + format!(r#"(allow file-read-metadata file-test-existence (literal "{ancestor}"))"#); + assert!( + profile.policy.contains(&rule), + "path resolution to the cryptex root must be able to stat {ancestor}" + ); + } + } + + #[test] + fn write_scoping_is_unchanged() { + let profile = generate_sandbox_profile(&config()); + + let mountpoint = param_for(&profile, "/Users/tester/.agentfs/run/sess-1/mnt"); + let run_dir = param_for(&profile, "/Users/tester/.agentfs/run/sess-1"); + let codex = param_for(&profile, "/Users/tester/.codex"); + for rule in [ + format!(r#"(allow file-write* (subpath (param "{mountpoint}")))"#), + format!(r#"(allow file-write* (subpath (param "{run_dir}")))"#), + format!(r#"(allow file-write* (subpath (param "{codex}")))"#), + r#"(allow file-write* (subpath "/private/tmp"))"#.to_string(), + r#"(allow file-write* (subpath "/tmp"))"#.to_string(), + r#"(allow file-write* (subpath "/var/tmp"))"#.to_string(), + r#"(allow file-write* (subpath "/private/var/folders"))"#.to_string(), + r#"(allow file-write* (subpath "/dev"))"#.to_string(), + ] { + assert!(profile.policy.contains(&rule), "missing write rule {rule}"); + } + } + + #[test] + fn dynamic_paths_never_appear_in_the_policy_text() { + let mut config = config(); + config.allow_paths.push(PathBuf::from( + r#"/Users/tester/pwn") (allow file-read* (subpath "/"#, + )); + + let profile = generate_sandbox_profile(&config); + + assert!( + !profile.policy.contains("pwn"), + "a user-controlled path leaked into the SBPL text:\n{}", + profile.policy + ); + // A dynamic path can only be interpolated raw as a quoted string, so + // no quote in the policy may be followed by a /Users-rooted path + // (static literals like /System/Volumes/Data/Users are fine). + assert!( + !profile.policy.contains(r#""/Users"#), + "a session/allow/home path was interpolated instead of parameterized:\n{}", + profile.policy + ); + assert!( + profile + .params + .iter() + .any(|(_, value)| value.to_string_lossy().contains("pwn")), + "the quote-bearing path must still be granted, via a -D param" + ); + + let mut names: Vec<&str> = profile.params.iter().map(|(n, _)| n.as_str()).collect(); + names.sort_unstable(); + names.dedup(); + assert_eq!( + names.len(), + profile.params.len(), + "sandbox-exec -D definitions must not repeat a param name" + ); + } + + #[test] + fn session_id_is_sanitized_in_the_policy_text() { + let mut config = config(); + config.session_id = r#"evil")(allow file-read*)(deny signal "x"#.to_string(); + + let profile = generate_sandbox_profile(&config); + + assert!( + profile.policy.contains( + r#"(deny default (with message "agentfs-evilallowfile-readdenysignalx: access denied"))"# + ), + "session id must be reduced to a conservative charset:\n{}", + profile.policy + ); + assert!( + profile + .policy + .lines() + .all(|line| line.trim() != "(allow file-read*)"), + "an injected session id must not open unscoped reads" + ); + } + + #[test] + fn fully_hostile_session_id_falls_back_to_a_fixed_log_tag() { + let mut config = config(); + config.session_id = r#""()[]{}<>#;$!"#.to_string(); + + let profile = generate_sandbox_profile(&config); + + assert!( + profile + .policy + .contains(r#"(deny default (with message "agentfs-session: access denied"))"#), + "a session id sanitized to nothing must fall back to a fixed tag:\n{}", + profile.policy + ); + assert!( + !profile.policy.contains("agentfs-:"), + "the log tag must never be empty:\n{}", + profile.policy + ); + } +} + +#[cfg(target_os = "macos")] +mod darwin_spawn_exit_codes { + use super::super::darwin::spawn_error_exit_code; + use anyhow::Context; + + fn spawn_error(kind: std::io::ErrorKind) -> anyhow::Error { + anyhow::Error::from(std::io::Error::new(kind, "spawn failed")) + } + + #[test] + fn missing_command_maps_to_127() { + assert_eq!( + spawn_error_exit_code(&spawn_error(std::io::ErrorKind::NotFound)), + Some(127) + ); + } + + #[test] + fn non_executable_command_maps_to_126() { + assert_eq!( + spawn_error_exit_code(&spawn_error(std::io::ErrorKind::PermissionDenied)), + Some(126) + ); + } + + #[test] + fn mapping_survives_anyhow_context_wrapping() { + let error: anyhow::Error = Err::<(), _>(spawn_error(std::io::ErrorKind::NotFound)) + .context("Darwin/NFS run supervision failed for cmd") + .unwrap_err(); + assert_eq!(spawn_error_exit_code(&error), Some(127)); + } + + #[test] + fn other_errors_go_to_the_reporter() { + assert_eq!( + spawn_error_exit_code(&spawn_error(std::io::ErrorKind::BrokenPipe)), + None + ); + assert_eq!(spawn_error_exit_code(&anyhow::anyhow!("not io")), None); + } +} + +#[cfg(target_os = "linux")] +mod skip_mount { + use super::super::linux::skip_mount; + use std::path::Path; + + #[test] + fn matches_virtual_fs_roots_and_descendants() { + for path in ["/proc", "/sys/kernel", "/dev", "/dev/shm", "/tmp/x"] { + assert!(skip_mount(Path::new(path)), "{path} should be skipped"); + } + } + + #[test] + fn sibling_lookalike_prefixes_are_remounted() { + for path in ["/devfoo", "/tmpfoo", "/procfs", "/system", "/data/tmp"] { + assert!( + !skip_mount(Path::new(path)), + "{path} must not be skipped by the ro-remount pass" + ); + } + } +} + +#[test] +fn group_paths_by_parent_uses_brace_expansion() { + let paths = vec![ + PathBuf::from("/home/user/.claude"), + PathBuf::from("/home/user/.claude.json"), + PathBuf::from("/home/user/.codex"), + PathBuf::from("/opt/tool"), + ]; + + assert_eq!( + group_paths_by_parent(&paths), + vec![ + "/home/user/{.claude, .claude.json, .codex}".to_string(), + "/opt/tool".to_string(), + ] + ); +} diff --git a/cli/src/cmd/safety.rs b/crates/agentfs-cli/src/cmd/safety.rs similarity index 65% rename from cli/src/cmd/safety.rs rename to crates/agentfs-cli/src/cmd/safety.rs index baff0573..3e1fdfbb 100644 --- a/cli/src/cmd/safety.rs +++ b/crates/agentfs-cli/src/cmd/safety.rs @@ -1,8 +1,7 @@ //! Production safety commands for local AgentFS databases. -use agentfs_sdk::{AgentFSOptions, AGENTFS_SCHEMA_VERSION}; +use agentfs_core::{schema::integrity, AgentFSOptions}; use anyhow::{Context, Result as AnyhowResult}; -use serde::Serialize; use std::collections::BTreeMap; use std::fs; use std::io::{Read, Seek, SeekFrom, Write}; @@ -12,8 +11,8 @@ use turso::{Builder, Connection, EncryptionOpts, Value}; const S_IFMT: i64 = 0o170000; const S_IFREG: i64 = 0o100000; +#[cfg(test)] const S_IFDIR: i64 = 0o040000; -const S_IFLNK: i64 = 0o120000; const STORAGE_CHUNKED: i64 = 0; const STORAGE_INLINE: i64 = 1; @@ -29,59 +28,6 @@ struct PartialOriginRow { base_ctime_nsec: i64, } -#[derive(Debug, Serialize)] -pub struct IntegrityReport { - database: String, - ok: bool, - portable: bool, - origin_backed: bool, - partial_origin_rows: i64, - checks: Vec, -} - -#[derive(Debug, Serialize)] -struct IntegrityCheck { - name: String, - ok: bool, - detail: String, - violating_rows: Option, -} - -impl IntegrityReport { - fn new(database: &Path) -> Self { - Self { - database: database.display().to_string(), - ok: true, - portable: true, - origin_backed: false, - partial_origin_rows: 0, - checks: Vec::new(), - } - } - - fn push_check( - &mut self, - name: impl Into, - ok: bool, - detail: impl Into, - violating_rows: Option, - ) { - self.ok &= ok; - self.checks.push(IntegrityCheck { - name: name.into(), - ok, - detail: detail.into(), - violating_rows, - }); - } -} - -#[derive(Debug, Clone, Copy)] -struct IntegrityOptions { - require_portable: bool, - check_base: bool, -} - /// Run integrity and schema-invariant checks for a local AgentFS database. pub async fn handle_integrity_command( stdout: &mut impl Write, @@ -89,30 +35,35 @@ pub async fn handle_integrity_command( json: bool, require_portable: bool, check_base: bool, + checkpoint: bool, encryption: Option<&(String, String)>, ) -> AnyhowResult<()> { - let db_path = resolve_local_db_path(&id_or_path)?; - let db = build_local_database(&db_path, encryption).await?; - let conn = db.connect().context("Failed to connect to database")?; - conn.execute("PRAGMA query_only = 1", ()) - .await - .context("Failed to enable query_only mode")?; - - let report = integrity_report( - &conn, - &db_path, - IntegrityOptions { - require_portable, - check_base, - }, - ) - .await?; + // Failures before a report exists (unresolvable path, unopenable or + // hard-corrupt database) still emit a minimal JSON object under --json so + // scripted consumers always get `ok` on stdout. + let (db_path, report) = + match open_and_check_integrity(&id_or_path, require_portable, check_base, encryption).await + { + Ok(checked) => checked, + Err(err) => { + if json { + let error_report = serde_json::json!({ + "ok": false, + "error": format!("{err:#}"), + }); + serde_json::to_writer_pretty(&mut *stdout, &error_report)?; + writeln!(stdout)?; + } + return Err(err); + } + }; write_integrity_report(stdout, &report, json)?; if !report.ok { anyhow::bail!("integrity checks failed for {}", db_path.display()); } - drop(conn); - drop(db); + if !checkpoint { + return Ok(()); + } let cleanup_db = build_local_database(&db_path, encryption).await?; let cleanup_conn = cleanup_db .connect() @@ -121,9 +72,47 @@ pub async fn handle_integrity_command( drop(cleanup_conn); drop(cleanup_db); remove_sqlite_sidecars_after_checkpoint(&db_path)?; + if !json { + writeln!(stdout, "Checkpoint: complete")?; + } Ok(()) } +async fn open_and_check_integrity( + id_or_path: &str, + require_portable: bool, + check_base: bool, + encryption: Option<&(String, String)>, +) -> AnyhowResult<(PathBuf, integrity::Report)> { + let db_path = resolve_local_db_path(id_or_path)?; + let sidecars = ReadOnlyOpenSidecars::capture(&db_path); + let result = check_integrity_readonly(&db_path, require_portable, check_base, encryption).await; + sidecars.remove_created_frameless(); + Ok((db_path, result?)) +} + +async fn check_integrity_readonly( + db_path: &Path, + require_portable: bool, + check_base: bool, + encryption: Option<&(String, String)>, +) -> AnyhowResult { + let db = build_local_database(db_path, encryption).await?; + let conn = db.connect().context("Failed to connect to database")?; + conn.execute("PRAGMA query_only = 1", ()) + .await + .context("Failed to enable query_only mode")?; + + let report = integrity::check( + &conn, + &integrity::CheckOpts::new(db_path.to_path_buf()) + .require_portable(require_portable) + .check_base(check_base), + ) + .await?; + Ok(report) +} + /// Create a portable main-database backup of a local AgentFS database. pub async fn handle_backup_command( stdout: &mut impl Write, @@ -180,13 +169,9 @@ pub async fn handle_backup_command( let backup_conn = backup_db .connect() .context("Failed to connect to backup database")?; - let report = integrity_report( + let report = integrity::check( &backup_conn, - &target, - IntegrityOptions { - require_portable: true, - check_base: false, - }, + &integrity::CheckOpts::new(target.clone()).require_portable(true), ) .await?; if !report.ok { @@ -225,7 +210,7 @@ pub async fn handle_materialize_command( Ok(()) } -async fn build_local_database( +pub(crate) async fn build_local_database( path: &Path, encryption: Option<&(String, String)>, ) -> AnyhowResult { @@ -292,13 +277,9 @@ async fn copy_and_materialize_database( ensure_no_partial_origin_rows(&target_conn).await?; checkpoint_materialized_target(&target_conn, target).await?; - let report = integrity_report( + let report = integrity::check( &target_conn, - target, - IntegrityOptions { - require_portable: true, - check_base: false, - }, + &integrity::CheckOpts::new(target.to_path_buf()).require_portable(true), ) .await?; if !report.ok { @@ -320,13 +301,9 @@ async fn copy_and_materialize_database( .connect() .context("Failed to connect to materialized target database")?; ensure_no_partial_origin_rows(&verify_conn).await?; - let report = integrity_report( + let report = integrity::check( &verify_conn, - target, - IntegrityOptions { - require_portable: true, - check_base: false, - }, + &integrity::CheckOpts::new(target.to_path_buf()).require_portable(true), ) .await?; if !report.ok { @@ -767,629 +744,6 @@ async fn checkpoint_materialized_target(conn: &Connection, target_path: &Path) - Ok(()) } -async fn integrity_report( - conn: &Connection, - db_path: &Path, - options: IntegrityOptions, -) -> AnyhowResult { - let mut report = IntegrityReport::new(db_path); - - let integrity_rows = query_string_column(conn, "PRAGMA integrity_check").await?; - report.push_check( - "pragma.integrity_check", - integrity_rows == ["ok".to_string()], - if integrity_rows == ["ok".to_string()] { - "ok".to_string() - } else { - format!("{integrity_rows:?}") - }, - None, - ); - - let required_tables = [ - "fs_config", - "fs_inode", - "fs_dentry", - "fs_data", - "fs_symlink", - "kv_store", - "tool_calls", - ]; - let mut tables = BTreeMap::new(); - for table in required_tables { - let exists = table_exists(conn, table).await?; - tables.insert(table.to_string(), exists); - report.push_check( - format!("schema.table.{table}"), - exists, - if exists { "present" } else { "missing" }, - if exists { Some(0) } else { Some(1) }, - ); - } - - if *tables.get("fs_config").unwrap_or(&false) { - check_config(conn, &mut report).await?; - } - - let has_inode = *tables.get("fs_inode").unwrap_or(&false); - let has_dentry = *tables.get("fs_dentry").unwrap_or(&false); - let has_data = *tables.get("fs_data").unwrap_or(&false); - let has_symlink = *tables.get("fs_symlink").unwrap_or(&false); - if has_inode && has_data { - check_storage_invariants(conn, &mut report).await?; - } - if has_inode && has_dentry { - check_namespace_invariants(conn, &mut report).await?; - } - if has_inode && has_symlink { - check_symlink_invariants(conn, &mut report).await?; - } - check_portability_status(conn, &mut report, options.require_portable).await?; - check_optional_overlay_invariants(conn, &mut report, options.check_base).await?; - - Ok(report) -} - -async fn check_config(conn: &Connection, report: &mut IntegrityReport) -> AnyhowResult<()> { - let schema_version = config_string(conn, "schema_version").await?; - report.push_check( - "config.schema_version", - schema_version.as_deref() == Some(AGENTFS_SCHEMA_VERSION), - schema_version - .as_deref() - .map(|value| format!("found {value}")) - .unwrap_or_else(|| "missing".to_string()), - if schema_version.as_deref() == Some(AGENTFS_SCHEMA_VERSION) { - Some(0) - } else { - Some(1) - }, - ); - - let chunk_size = config_i64(conn, "chunk_size").await?; - report.push_check( - "config.chunk_size", - chunk_size.is_some_and(|value| value > 0), - chunk_size - .map(|value| format!("found {value}")) - .unwrap_or_else(|| "missing or invalid".to_string()), - if chunk_size.is_some_and(|value| value > 0) { - Some(0) - } else { - Some(1) - }, - ); - - let inline_threshold = config_i64(conn, "inline_threshold").await?; - let inline_ok = match (inline_threshold, chunk_size) { - (Some(inline), Some(chunk)) => inline >= 0 && inline <= chunk, - _ => false, - }; - report.push_check( - "config.inline_threshold", - inline_ok, - inline_threshold - .map(|value| format!("found {value}")) - .unwrap_or_else(|| "missing or invalid".to_string()), - if inline_ok { Some(0) } else { Some(1) }, - ); - - Ok(()) -} - -async fn check_storage_invariants( - conn: &Connection, - report: &mut IntegrityReport, -) -> AnyhowResult<()> { - add_zero_count_check( - conn, - report, - "storage.kind_valid", - "SELECT COUNT(*) FROM fs_inode WHERE storage_kind NOT IN (0, 1)", - ) - .await?; - add_zero_count_check( - conn, - report, - "storage.inline_has_no_chunks", - "SELECT COUNT(*) - FROM fs_inode i - WHERE i.storage_kind = 1 - AND EXISTS (SELECT 1 FROM fs_data d WHERE d.ino = i.ino)", - ) - .await?; - add_zero_count_check( - conn, - report, - "storage.chunked_has_no_inline_data", - "SELECT COUNT(*) FROM fs_inode WHERE storage_kind = 0 AND data_inline IS NOT NULL", - ) - .await?; - add_zero_count_check( - conn, - report, - "storage.inline_size_matches_blob", - "SELECT COUNT(*) - FROM fs_inode - WHERE storage_kind = 1 - AND (data_inline IS NULL OR COALESCE(length(data_inline), 0) != size)", - ) - .await?; - add_zero_count_check( - conn, - report, - "storage.inline_only_regular_files", - &format!( - "SELECT COUNT(*) FROM fs_inode WHERE storage_kind = 1 AND (mode & {S_IFMT}) != {S_IFREG}" - ), - ) - .await?; - add_zero_count_check( - conn, - report, - "storage.non_regular_has_no_inline_data", - &format!( - "SELECT COUNT(*) FROM fs_inode WHERE (mode & {S_IFMT}) != {S_IFREG} AND data_inline IS NOT NULL" - ), - ) - .await?; - add_zero_count_check( - conn, - report, - "storage.chunks_reference_inodes", - "SELECT COUNT(*) - FROM fs_data d - LEFT JOIN fs_inode i ON i.ino = d.ino - WHERE i.ino IS NULL", - ) - .await?; - add_zero_count_check( - conn, - report, - "storage.chunks_nonnegative_index", - "SELECT COUNT(*) FROM fs_data WHERE chunk_index < 0", - ) - .await?; - - if let Some(chunk_size) = config_i64(conn, "chunk_size").await? { - if chunk_size > 0 { - add_zero_count_check( - conn, - report, - "storage.chunk_length_within_chunk_size", - &format!("SELECT COUNT(*) FROM fs_data WHERE length(data) > {chunk_size}"), - ) - .await?; - } - } - add_zero_count_check( - conn, - report, - "storage.chunks_only_regular_files", - &format!( - "SELECT COUNT(*) - FROM fs_data d - JOIN fs_inode i ON i.ino = d.ino - WHERE (i.mode & {S_IFMT}) != {S_IFREG}" - ), - ) - .await?; - - Ok(()) -} - -async fn check_namespace_invariants( - conn: &Connection, - report: &mut IntegrityReport, -) -> AnyhowResult<()> { - add_exact_count_check( - conn, - report, - "namespace.root_inode", - &format!("SELECT COUNT(*) FROM fs_inode WHERE ino = 1 AND (mode & {S_IFMT}) = {S_IFDIR}"), - 1, - ) - .await?; - add_zero_count_check( - conn, - report, - "namespace.dentry_parent_exists", - "SELECT COUNT(*) - FROM fs_dentry d - LEFT JOIN fs_inode p ON p.ino = d.parent_ino - WHERE p.ino IS NULL", - ) - .await?; - add_zero_count_check( - conn, - report, - "namespace.dentry_parent_is_directory", - &format!( - "SELECT COUNT(*) - FROM fs_dentry d - JOIN fs_inode p ON p.ino = d.parent_ino - WHERE (p.mode & {S_IFMT}) != {S_IFDIR}" - ), - ) - .await?; - add_zero_count_check( - conn, - report, - "namespace.dentry_target_exists", - "SELECT COUNT(*) - FROM fs_dentry d - LEFT JOIN fs_inode i ON i.ino = d.ino - WHERE i.ino IS NULL", - ) - .await?; - add_zero_count_check( - conn, - report, - "namespace.non_root_inode_has_dentry", - // nlink = 0 rows are POSIX orphans: files unlinked while open, whose - // reap is deferred until the last handle closes (and swept at the - // next mount after a crash). Dentry-less is legal only in that state. - "SELECT COUNT(*) - FROM fs_inode i - WHERE i.ino != 1 - AND i.nlink != 0 - AND NOT EXISTS (SELECT 1 FROM fs_dentry d WHERE d.ino = i.ino)", - ) - .await?; - add_zero_count_check( - conn, - report, - "namespace.dentry_names_valid", - "SELECT COUNT(*) - FROM fs_dentry - WHERE name = '' OR name = '.' OR name = '..' OR instr(name, '/') > 0", - ) - .await?; - add_zero_count_check( - conn, - report, - "namespace.non_directory_nlink_matches_dentries", - &format!( - "SELECT COUNT(*) - FROM fs_inode i - WHERE (i.mode & {S_IFMT}) != {S_IFDIR} - AND i.nlink != (SELECT COUNT(*) FROM fs_dentry d WHERE d.ino = i.ino)" - ), - ) - .await?; - add_zero_count_check( - conn, - report, - "namespace.directory_nlink_positive", - &format!("SELECT COUNT(*) FROM fs_inode WHERE (mode & {S_IFMT}) = {S_IFDIR} AND nlink < 1"), - ) - .await?; - - Ok(()) -} - -async fn check_symlink_invariants( - conn: &Connection, - report: &mut IntegrityReport, -) -> AnyhowResult<()> { - add_zero_count_check( - conn, - report, - "symlink.rows_reference_symlink_inodes", - &format!( - "SELECT COUNT(*) - FROM fs_symlink s - LEFT JOIN fs_inode i ON i.ino = s.ino - WHERE i.ino IS NULL OR (i.mode & {S_IFMT}) != {S_IFLNK}" - ), - ) - .await?; - add_zero_count_check( - conn, - report, - "symlink.inodes_have_rows", - &format!( - "SELECT COUNT(*) - FROM fs_inode i - WHERE (i.mode & {S_IFMT}) = {S_IFLNK} - AND NOT EXISTS (SELECT 1 FROM fs_symlink s WHERE s.ino = i.ino)" - ), - ) - .await -} - -async fn check_portability_status( - conn: &Connection, - report: &mut IntegrityReport, - require_portable: bool, -) -> AnyhowResult<()> { - let partial_origin_rows = if table_exists(conn, "fs_partial_origin").await? { - scalar_i64(conn, "SELECT COUNT(*) FROM fs_partial_origin").await? - } else { - 0 - }; - - report.partial_origin_rows = partial_origin_rows; - report.origin_backed = partial_origin_rows > 0; - report.portable = partial_origin_rows == 0; - - report.push_check( - "overlay.portability_status", - true, - if report.portable { - "portable: no partial-origin rows".to_string() - } else { - format!("origin-backed: {partial_origin_rows} partial-origin row(s)") - }, - Some(partial_origin_rows), - ); - - if require_portable { - report.push_check( - "overlay.require_portable", - report.portable, - if report.portable { - "portable requirement satisfied".to_string() - } else { - format!("portable requirement failed: {partial_origin_rows} partial-origin row(s)") - }, - Some(partial_origin_rows), - ); - } - - Ok(()) -} - -async fn check_optional_overlay_invariants( - conn: &Connection, - report: &mut IntegrityReport, - check_base: bool, -) -> AnyhowResult<()> { - if table_exists(conn, "fs_origin").await? { - add_zero_count_check( - conn, - report, - "overlay.origin_delta_inode_exists", - "SELECT COUNT(*) - FROM fs_origin o - LEFT JOIN fs_inode i ON i.ino = o.delta_ino - WHERE i.ino IS NULL", - ) - .await?; - } - - if table_exists(conn, "fs_partial_origin").await? { - add_zero_count_check( - conn, - report, - "overlay.partial_origin_delta_inode_exists", - "SELECT COUNT(*) - FROM fs_partial_origin p - LEFT JOIN fs_inode i ON i.ino = p.delta_ino - WHERE i.ino IS NULL", - ) - .await?; - add_zero_count_check( - conn, - report, - "overlay.partial_origin_delta_inode_regular", - &format!( - "SELECT COUNT(*) - FROM fs_partial_origin p - LEFT JOIN fs_inode i ON i.ino = p.delta_ino - WHERE i.ino IS NULL OR (i.mode & {S_IFMT}) != {S_IFREG}" - ), - ) - .await?; - add_zero_count_check( - conn, - report, - "overlay.partial_origin_sizes_valid", - "SELECT COUNT(*) - FROM fs_partial_origin - WHERE base_size < 0 OR base_fingerprint_size < -1", - ) - .await?; - add_zero_count_check( - conn, - report, - "overlay.partial_origin_paths_absolute", - "SELECT COUNT(*) - FROM fs_partial_origin - WHERE base_path = '' OR base_path NOT LIKE '/%' OR instr(base_path, '/../') > 0 OR base_path LIKE '%/..'", - ) - .await?; - if check_base { - check_partial_origin_base_fingerprints(conn, report).await?; - } - } - - if table_exists(conn, "fs_chunk_override").await? { - add_zero_count_check( - conn, - report, - "overlay.chunk_override_delta_inode_exists", - "SELECT COUNT(*) - FROM fs_chunk_override c - LEFT JOIN fs_inode i ON i.ino = c.delta_ino - WHERE i.ino IS NULL", - ) - .await?; - add_zero_count_check( - conn, - report, - "overlay.chunk_override_nonnegative_index", - "SELECT COUNT(*) FROM fs_chunk_override WHERE chunk_index < 0", - ) - .await?; - if table_exists(conn, "fs_partial_origin").await? { - add_zero_count_check( - conn, - report, - "overlay.chunk_override_references_partial_origin", - "SELECT COUNT(*) - FROM fs_chunk_override c - LEFT JOIN fs_partial_origin p ON p.delta_ino = c.delta_ino - WHERE p.delta_ino IS NULL", - ) - .await?; - } else { - add_zero_count_check( - conn, - report, - "overlay.chunk_override_requires_partial_origin_table", - "SELECT COUNT(*) FROM fs_chunk_override", - ) - .await?; - } - add_zero_count_check( - conn, - report, - "overlay.chunk_override_unique", - "SELECT COUNT(*) - FROM ( - SELECT delta_ino, chunk_index, COUNT(*) AS n - FROM fs_chunk_override - GROUP BY delta_ino, chunk_index - HAVING n > 1 - )", - ) - .await?; - if let Some(chunk_size) = config_i64(conn, "chunk_size").await? { - if chunk_size > 0 { - add_zero_count_check( - conn, - report, - "overlay.chunk_override_index_in_range", - &format!( - "SELECT COUNT(*) - FROM fs_chunk_override c - JOIN fs_inode i ON i.ino = c.delta_ino - WHERE c.chunk_index * {chunk_size} >= i.size" - ), - ) - .await?; - } - } - } - - if table_exists(conn, "fs_whiteout").await? { - add_zero_count_check( - conn, - report, - "overlay.whiteout_paths_absolute", - "SELECT COUNT(*) - FROM fs_whiteout - WHERE path NOT LIKE '/%' OR parent_path NOT LIKE '/%'", - ) - .await?; - } - - Ok(()) -} - -async fn check_partial_origin_base_fingerprints( - conn: &Connection, - report: &mut IntegrityReport, -) -> AnyhowResult<()> { - let partial_rows = load_partial_origin_rows(conn).await?; - if partial_rows.is_empty() { - report.push_check( - "overlay.partial_origin_base_fingerprints", - true, - "no partial-origin rows".to_string(), - Some(0), - ); - return Ok(()); - } - - let base_root = match read_overlay_base_root(conn).await { - Ok(root) => root, - Err(err) => { - report.push_check( - "overlay.partial_origin_base_fingerprints", - false, - err.to_string(), - Some(partial_rows.len() as i64), - ); - return Ok(()); - } - }; - - let mut violations = 0; - let mut first_error = None; - for partial in &partial_rows { - let result = (|| -> AnyhowResult<()> { - let base_path = resolve_materialization_base_path(&base_root, &partial.base_path)?; - let metadata = fs::metadata(&base_path) - .with_context(|| format!("Failed to stat base file {}", base_path.display()))?; - validate_base_fingerprint(partial, &metadata, &base_path) - })(); - if let Err(err) = result { - violations += 1; - if first_error.is_none() { - first_error = Some(err.to_string()); - } - } - } - - report.push_check( - "overlay.partial_origin_base_fingerprints", - violations == 0, - if violations == 0 { - format!("{} base fingerprint(s) valid", partial_rows.len()) - } else { - format!( - "{violations} base fingerprint violation(s); first: {}", - first_error.unwrap_or_else(|| "unknown".to_string()) - ) - }, - Some(violations), - ); - Ok(()) -} - -async fn add_zero_count_check( - conn: &Connection, - report: &mut IntegrityReport, - name: &str, - sql: &str, -) -> AnyhowResult<()> { - let count = scalar_i64(conn, sql).await?; - report.push_check( - name, - count == 0, - if count == 0 { - "0 violating rows".to_string() - } else { - format!("{count} violating rows") - }, - Some(count), - ); - Ok(()) -} - -async fn add_exact_count_check( - conn: &Connection, - report: &mut IntegrityReport, - name: &str, - sql: &str, - expected: i64, -) -> AnyhowResult<()> { - let count = scalar_i64(conn, sql).await?; - report.push_check( - name, - count == expected, - format!("found {count}, expected {expected}"), - if count == expected { - Some(0) - } else { - Some((count - expected).abs()) - }, - ); - Ok(()) -} - async fn checkpoint_for_backup(conn: &Connection, source_path: &Path) -> AnyhowResult<()> { conn.execute("PRAGMA synchronous = FULL", ()).await?; @@ -1456,7 +810,7 @@ fn copy_main_db_exclusive(source: &Path, target: &Path) -> AnyhowResult<()> { fn write_integrity_report( stdout: &mut impl Write, - report: &IntegrityReport, + report: &integrity::Report, json: bool, ) -> AnyhowResult<()> { if json { @@ -1519,7 +873,12 @@ fn ensure_backup_target(source_path: &Path, target: &Path) -> AnyhowResult<()> { ); } } - let parent = target.parent().unwrap_or_else(|| Path::new(".")); + // A bare relative target ("backup.db") has Some("") as its parent, not + // None; both mean the current directory. + let parent = match target.parent() { + Some(parent) if !parent.as_os_str().is_empty() => parent, + _ => Path::new("."), + }; if !parent.is_dir() { anyhow::bail!("Backup target parent does not exist: {}", parent.display()); } @@ -1554,7 +913,51 @@ fn sidecar_path(path: &Path, suffix: &str) -> PathBuf { PathBuf::from(format!("{}{}", path.display(), suffix)) } -fn remove_sqlite_sidecars_after_checkpoint(path: &Path) -> AnyhowResult<()> { +/// Sidecar census around a raw turso open on a read path. turso materializes +/// an empty `-wal` next to the main file the moment a database is opened, +/// even for query-only connections; the frameless sidecars the open itself +/// created must be removed on exit (single-file invariant I1) without ever +/// touching a pre-existing family, which read paths must not mutate. +pub(crate) struct ReadOnlyOpenSidecars { + wal: PathBuf, + shm: PathBuf, + wal_preexisted: bool, + shm_preexisted: bool, +} + +impl ReadOnlyOpenSidecars { + pub(crate) fn capture(db_path: &Path) -> Self { + let wal = sidecar_path(db_path, "-wal"); + let shm = sidecar_path(db_path, "-shm"); + let wal_preexisted = wal.exists(); + let shm_preexisted = shm.exists(); + Self { + wal, + shm, + wal_preexisted, + shm_preexisted, + } + } + + /// Best-effort removal of sidecars this open created. A WAL is removed + /// only while frameless (empty or the bare 32-byte header): once frames + /// exist, unlinking would drop committed data. + pub(crate) fn remove_created_frameless(&self) { + const WAL_HEADER_SIZE: u64 = 32; + if !self.wal_preexisted { + if let Ok(metadata) = fs::metadata(&self.wal) { + if metadata.len() <= WAL_HEADER_SIZE { + let _ = fs::remove_file(&self.wal); + } + } + } + if !self.shm_preexisted && self.shm.exists() { + let _ = fs::remove_file(&self.shm); + } + } +} + +pub(crate) fn remove_sqlite_sidecars_after_checkpoint(path: &Path) -> AnyhowResult<()> { let wal = sidecar_path(path, "-wal"); if let Ok(metadata) = fs::metadata(&wal) { if metadata.len() != 0 { @@ -1591,15 +994,6 @@ async fn table_exists(conn: &Connection, table: &str) -> AnyhowResult { Ok(rows.next().await?.is_some()) } -async fn query_string_column(conn: &Connection, sql: &str) -> AnyhowResult> { - let mut rows = conn.query(sql, ()).await?; - let mut values = Vec::new(); - while let Some(row) = rows.next().await? { - values.push(row.get::(0)?); - } - Ok(values) -} - async fn scalar_i64(conn: &Connection, sql: &str) -> AnyhowResult { let mut rows = conn.query(sql, ()).await?; let row = rows.next().await?.context("query returned no rows")?; @@ -1634,9 +1028,56 @@ fn value_i64(value: Value) -> AnyhowResult { #[cfg(test)] mod tests { use super::*; - use agentfs_sdk::{AgentFS, AgentFSOptions}; + use agentfs_core::{AgentFS, AgentFSOptions}; use serde_json::Value as JsonValue; + async fn write_agent_file(agent: &AgentFS, path: &str, data: &[u8]) { + let (_, file) = agent + .fs + .create_file(path, (S_IFREG | 0o644) as u32, 0, 0) + .await + .unwrap(); + file.pwrite(0, data).await.unwrap(); + agent.fs.drain_all().await.unwrap(); + } + + fn db_family_stats(db_path: &Path) -> Vec<(String, u64, Option)> { + ["", "-wal", "-shm"] + .into_iter() + .filter_map(|suffix| { + let path = PathBuf::from(format!("{}{}", db_path.display(), suffix)); + let metadata = fs::metadata(&path).ok()?; + Some((suffix.to_string(), metadata.len(), metadata.modified().ok())) + }) + .collect() + } + + #[test] + fn backup_target_with_bare_relative_name_treats_parent_as_cwd() { + let temp_dir = tempfile::tempdir().unwrap(); + let source = temp_dir.path().join("source.db"); + fs::write(&source, b"stub").unwrap(); + + let bare = format!("agentfs-backup-target-{}.db", std::process::id()); + ensure_backup_target(&source, Path::new(&bare)).expect( + "a bare relative backup target must resolve its parent to the current directory", + ); + } + + #[test] + fn backup_target_with_missing_parent_still_errors() { + let temp_dir = tempfile::tempdir().unwrap(); + let source = temp_dir.path().join("source.db"); + fs::write(&source, b"stub").unwrap(); + + let target = temp_dir.path().join("no-such-dir").join("backup.db"); + let err = ensure_backup_target(&source, &target).unwrap_err(); + assert!( + err.to_string().contains("no-such-dir"), + "missing-parent error must name the directory: {err:#}" + ); + } + #[tokio::test] async fn integrity_succeeds_for_valid_database() { let temp_dir = tempfile::tempdir().unwrap(); @@ -1645,7 +1086,7 @@ mod tests { let agent = AgentFS::open(AgentFSOptions::with_path(db_path.to_string_lossy())) .await .unwrap(); - agent.fs.pwrite("/hello.txt", 0, b"hello").await.unwrap(); + write_agent_file(&agent, "/hello.txt", b"hello").await; } let mut stdout = Vec::new(); @@ -1655,6 +1096,7 @@ mod tests { true, false, false, + false, None, ) .await @@ -1663,6 +1105,180 @@ mod tests { assert_eq!(json["ok"], true); } + #[tokio::test] + async fn integrity_is_read_only_unless_checkpoint_requested() { + let temp_dir = tempfile::tempdir().unwrap(); + let db_path = temp_dir.path().join("read-only.db"); + { + let agent = AgentFS::open(AgentFSOptions::with_path(db_path.to_string_lossy())) + .await + .unwrap(); + write_agent_file(&agent, "/hello.txt", b"hello").await; + } + + let before = db_family_stats(&db_path); + let mut stdout = Vec::new(); + handle_integrity_command( + &mut stdout, + db_path.to_string_lossy().to_string(), + false, + false, + false, + false, + None, + ) + .await + .unwrap(); + assert_eq!(db_family_stats(&db_path), before); + + let mut checkpoint_stdout = Vec::new(); + handle_integrity_command( + &mut checkpoint_stdout, + db_path.to_string_lossy().to_string(), + false, + false, + false, + true, + None, + ) + .await + .unwrap(); + let output = String::from_utf8(checkpoint_stdout).unwrap(); + assert!(output.contains("Checkpoint: complete")); + } + + #[tokio::test] + async fn integrity_json_emits_error_object_for_hard_corruption() { + let temp_dir = tempfile::tempdir().unwrap(); + let db_path = temp_dir.path().join("hard-corrupt.db"); + { + let agent = AgentFS::open(AgentFSOptions::with_path(db_path.to_string_lossy())) + .await + .unwrap(); + write_agent_file(&agent, "/hello.txt", b"hello").await; + } + { + use std::io::{Seek, SeekFrom, Write as IoWrite}; + let mut file = fs::OpenOptions::new().write(true).open(&db_path).unwrap(); + file.seek(SeekFrom::Start(4096)).unwrap(); + file.write_all(&[0xa0; 4096]).unwrap(); + } + + let mut stdout = Vec::new(); + let err = handle_integrity_command( + &mut stdout, + db_path.to_string_lossy().to_string(), + true, + false, + false, + false, + None, + ) + .await + .unwrap_err(); + let json: JsonValue = serde_json::from_slice(&stdout) + .unwrap_or_else(|parse| panic!("--json must emit a JSON object on stdout even for hard corruption; stdout={:?} parse={parse} err={err:#}", String::from_utf8_lossy(&stdout))); + assert_eq!(json["ok"], false); + let error = json["error"].as_str().unwrap(); + assert!(!error.is_empty()); + } + + #[tokio::test] + async fn integrity_read_only_tolerates_stale_shm_without_mutating_family() { + let temp_dir = tempfile::tempdir().unwrap(); + let db_path = temp_dir.path().join("stale-shm.db"); + { + let agent = AgentFS::open(AgentFSOptions::with_path(db_path.to_string_lossy())) + .await + .unwrap(); + write_agent_file(&agent, "/hello.txt", b"hello").await; + } + // Crash residue from a SIGKILLed writer: a wal-index sidecar whose + // contents cannot be trusted. The read-only open must not consult it + // and must not repair or remove it. + let shm_path = PathBuf::from(format!("{}-shm", db_path.display())); + fs::write(&shm_path, [0xde, 0xad, 0xbe, 0xef].repeat(8192)).unwrap(); + + let family_before: Vec<(String, Vec)> = ["", "-wal", "-shm"] + .into_iter() + .filter_map(|suffix| { + let path = PathBuf::from(format!("{}{}", db_path.display(), suffix)); + fs::read(&path) + .ok() + .map(|bytes| (suffix.to_string(), bytes)) + }) + .collect(); + + for json in [false, true] { + let mut stdout = Vec::new(); + handle_integrity_command( + &mut stdout, + db_path.to_string_lossy().to_string(), + json, + false, + false, + false, + None, + ) + .await + .unwrap_or_else(|err| { + panic!("read-only integrity must tolerate a stale -shm (json={json}): {err:#}") + }); + } + + let family_after: Vec<(String, Vec)> = ["", "-wal", "-shm"] + .into_iter() + .filter_map(|suffix| { + let path = PathBuf::from(format!("{}{}", db_path.display(), suffix)); + fs::read(&path) + .ok() + .map(|bytes| (suffix.to_string(), bytes)) + }) + .collect(); + assert_eq!( + family_before, family_after, + "read-only integrity must not mutate the database family" + ); + } + + #[tokio::test] + async fn integrity_read_only_creates_no_sidecars() { + let temp_dir = tempfile::tempdir().unwrap(); + let db_path = temp_dir.path().join("single-file.db"); + { + let agent = AgentFS::open(AgentFSOptions::with_path(db_path.to_string_lossy())) + .await + .unwrap(); + write_agent_file(&agent, "/hello.txt", b"hello").await; + agent.fs.finalize().await.unwrap(); + } + let wal = PathBuf::from(format!("{}-wal", db_path.display())); + let shm = PathBuf::from(format!("{}-shm", db_path.display())); + assert!(!wal.exists(), "fixture must start as a single file"); + + let mut stdout = Vec::new(); + handle_integrity_command( + &mut stdout, + db_path.to_string_lossy().to_string(), + false, + false, + false, + false, + None, + ) + .await + .unwrap(); + + assert!( + !wal.exists(), + "read-only integrity must not leave a WAL sidecar it created" + ); + assert!( + !shm.exists(), + "read-only integrity must not leave an SHM sidecar it created" + ); + } + #[tokio::test] async fn integrity_fails_for_inline_file_with_chunk_rows() { let temp_dir = tempfile::tempdir().unwrap(); @@ -1671,7 +1287,7 @@ mod tests { let agent = AgentFS::open(AgentFSOptions::with_path(db_path.to_string_lossy())) .await .unwrap(); - agent.fs.pwrite("/bad.txt", 0, b"bad").await.unwrap(); + write_agent_file(&agent, "/bad.txt", b"bad").await; let conn = agent.get_connection().await.unwrap(); let mut rows = conn .query( @@ -1697,6 +1313,7 @@ mod tests { true, false, false, + false, None, ) .await @@ -1738,6 +1355,7 @@ mod tests { true, false, false, + false, None, ) .await @@ -1760,8 +1378,8 @@ mod tests { let agent = AgentFS::open(AgentFSOptions::with_path(source.to_string_lossy())) .await .unwrap(); - agent.fs.pwrite("/small.txt", 0, b"small").await.unwrap(); - agent.fs.pwrite("/large.bin", 0, &large).await.unwrap(); + write_agent_file(&agent, "/small.txt", b"small").await; + write_agent_file(&agent, "/large.bin", &large).await; } let mut stdout = Vec::new(); @@ -1806,7 +1424,7 @@ mod tests { ) .await .unwrap(); - agent.fs.pwrite("/secret.txt", 0, b"secret").await.unwrap(); + write_agent_file(&agent, "/secret.txt", b"secret").await; } let encryption = (key.to_string(), cipher.to_string()); @@ -1817,6 +1435,7 @@ mod tests { true, false, false, + false, Some(&encryption), ) .await @@ -1857,23 +1476,6 @@ mod tests { .await .unwrap(); let conn = agent.get_connection().await.unwrap(); - conn.execute( - "CREATE TABLE fs_partial_origin ( - delta_ino INTEGER PRIMARY KEY, - base_ino INTEGER NOT NULL, - base_path TEXT NOT NULL, - base_size INTEGER NOT NULL, - base_fingerprint_size INTEGER NOT NULL DEFAULT -1, - base_mtime INTEGER NOT NULL DEFAULT 0, - base_mtime_nsec INTEGER NOT NULL DEFAULT 0, - base_ctime INTEGER NOT NULL DEFAULT 0, - base_ctime_nsec INTEGER NOT NULL DEFAULT 0, - created_at INTEGER NOT NULL - )", - (), - ) - .await - .unwrap(); conn.execute( "INSERT INTO fs_partial_origin (delta_ino, base_ino, base_path, base_size, created_at) @@ -1973,47 +1575,11 @@ mod tests { .await .unwrap(); conn.execute( - "CREATE TABLE fs_overlay_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL - )", - (), - ) - .await - .unwrap(); - conn.execute( - "INSERT INTO fs_overlay_config (key, value) VALUES ('base_path', ?)", + "INSERT OR REPLACE INTO fs_overlay_config (key, value) VALUES ('base_path', ?)", (base_dir.to_string_lossy().to_string(),), ) .await .unwrap(); - conn.execute( - "CREATE TABLE fs_partial_origin ( - delta_ino INTEGER PRIMARY KEY, - base_ino INTEGER NOT NULL, - base_path TEXT NOT NULL, - base_size INTEGER NOT NULL, - base_fingerprint_size INTEGER NOT NULL DEFAULT -1, - base_mtime INTEGER NOT NULL DEFAULT 0, - base_mtime_nsec INTEGER NOT NULL DEFAULT 0, - base_ctime INTEGER NOT NULL DEFAULT 0, - base_ctime_nsec INTEGER NOT NULL DEFAULT 0, - created_at INTEGER NOT NULL - )", - (), - ) - .await - .unwrap(); - conn.execute( - "CREATE TABLE fs_chunk_override ( - delta_ino INTEGER NOT NULL, - chunk_index INTEGER NOT NULL, - PRIMARY KEY (delta_ino, chunk_index) - )", - (), - ) - .await - .unwrap(); conn.execute( "INSERT INTO fs_inode ( ino, mode, nlink, uid, gid, size, atime, mtime, ctime, rdev, @@ -2081,13 +1647,9 @@ mod tests { expected ); let conn = agent.get_connection().await.unwrap(); - let report = integrity_report( + let report = integrity::check( &conn, - db_path, - IntegrityOptions { - require_portable: true, - check_base: false, - }, + &integrity::CheckOpts::new(db_path.to_path_buf()).require_portable(true), ) .await .unwrap(); diff --git a/cli/src/cmd/sync.rs b/crates/agentfs-cli/src/cmd/sync.rs similarity index 56% rename from cli/src/cmd/sync.rs rename to crates/agentfs-cli/src/cmd/sync.rs index 1c6b4d45..deb3e6fc 100644 --- a/cli/src/cmd/sync.rs +++ b/crates/agentfs-cli/src/cmd/sync.rs @@ -1,4 +1,4 @@ -use agentfs_sdk::AgentFSOptions; +use agentfs_core::AgentFSOptions; use crate::cmd::init::open_agentfs; @@ -6,7 +6,9 @@ pub async fn handle_pull_command(id_or_path: String) -> anyhow::Result<()> { let options = AgentFSOptions::resolve(&id_or_path)?; eprintln!("Using agent: {}", id_or_path); - let agent = open_agentfs(options).await?; + let agent = open_agentfs(options) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; agent.pull().await?; eprintln!("Remote data pulled to local db successfully"); Ok(()) @@ -16,7 +18,9 @@ pub async fn handle_push_command(id_or_path: String) -> anyhow::Result<()> { let options = AgentFSOptions::resolve(&id_or_path)?; eprintln!("Using agent: {}", id_or_path); - let agent = open_agentfs(options).await?; + let agent = open_agentfs(options) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; agent.push().await?; eprintln!("Local data pushed to remote db successfully"); Ok(()) @@ -26,7 +30,9 @@ pub async fn handle_checkpoint_command(id_or_path: String) -> anyhow::Result<()> let options = AgentFSOptions::resolve(&id_or_path)?; eprintln!("Using agent: {}", id_or_path); - let agent = open_agentfs(options).await?; + let agent = open_agentfs(options) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; agent.checkpoint().await?; eprintln!("Local db checkpointed successfully"); Ok(()) @@ -39,8 +45,15 @@ pub async fn handle_stats_command( let options = AgentFSOptions::resolve(&id_or_path)?; eprintln!("Using agent: {}", id_or_path); - let agent = open_agentfs(options).await?; - let stats = agent.sync_stats().await?; - stdout.write_all(serde_json::to_string(&stats)?.as_bytes())?; - Ok(()) + let agent = open_agentfs(options) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, &id_or_path))?; + let result: anyhow::Result<()> = async { + let stats = agent.sync_stats().await?; + stdout.write_all(serde_json::to_string(&stats)?.as_bytes())?; + Ok(()) + } + .await; + crate::cmd::init::finalize_readonly(&agent).await; + result } diff --git a/cli/src/cmd/timeline.rs b/crates/agentfs-cli/src/cmd/timeline.rs similarity index 76% rename from cli/src/cmd/timeline.rs rename to crates/agentfs-cli/src/cmd/timeline.rs index 7b9e9fb1..64e9aa9d 100644 --- a/cli/src/cmd/timeline.rs +++ b/crates/agentfs-cli/src/cmd/timeline.rs @@ -1,10 +1,10 @@ -use agentfs_sdk::{toolcalls::ToolCall, AgentFSOptions, ToolCalls}; +use agentfs_core::{toolcalls::ToolCall, AgentFSOptions, ToolCalls}; use anyhow::{Context, Result as AnyhowResult}; use chrono::TimeZone; use std::io::Write; use std::str::FromStr; -use crate::cmd::init::open_agentfs; +use crate::cmd::init::{finalize_readonly, open_agentfs}; /// Output format for timeline display #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -42,8 +42,20 @@ pub async fn show_timeline( ) -> AnyhowResult<()> { let agent_options = AgentFSOptions::resolve(id_or_path)?; - let agentfs = open_agentfs(agent_options).await?; + let agentfs = open_agentfs(agent_options) + .await + .map_err(|err| super::migrate::open_error_with_guidance(err, id_or_path))?; + + let result = show_timeline_opened(stdout, &agentfs, options).await; + finalize_readonly(&agentfs).await; + result +} +async fn show_timeline_opened( + stdout: &mut impl Write, + agentfs: &agentfs_core::AgentFS, + options: &TimelineOptions, +) -> AnyhowResult<()> { let toolcalls = ToolCalls::from_pool(agentfs.get_pool()) .await .context("Failed to create tool calls tracker")?; @@ -82,13 +94,15 @@ fn truncate_with_ellipsis(s: &str, max_len: usize) -> String { } } -/// Format timestamp as YYYY-MM-DD HH:MM:SS -fn format_timestamp(timestamp: i64) -> String { +/// Format a unix-seconds timestamp as YYYY-MM-DD HH:MM:SS. `tool_calls` +/// stores milliseconds, but `ToolCalls` normalizes to seconds on read, so +/// both this table column and the JSON form render second granularity. +fn format_timestamp(timestamp_secs: i64) -> String { chrono::Utc - .timestamp_opt(timestamp, 0) + .timestamp_opt(timestamp_secs, 0) .single() .map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string()) - .unwrap_or_else(|| format!("Invalid timestamp: {}", timestamp)) + .unwrap_or_else(|| format!("Invalid timestamp: {}", timestamp_secs)) } /// Format tool calls in table format @@ -136,7 +150,7 @@ fn format_json(stdout: &mut impl Write, calls: &[ToolCall]) -> AnyhowResult<()> #[cfg(test)] mod tests { use super::*; - use agentfs_sdk::{AgentFS, AgentFSOptions}; + use agentfs_core::{AgentFS, AgentFSOptions}; use tempfile::NamedTempFile; async fn create_test_agentfs() -> (AgentFS, String, NamedTempFile) { @@ -307,6 +321,72 @@ mod tests { assert!(!output.contains("tool_0")); } + #[tokio::test] + async fn test_timeline_leaves_no_wal_sidecar() { + let (agentfs, path, _file) = create_test_agentfs().await; + agentfs.tools.start("test_tool", None).await.unwrap(); + drop(agentfs); + + let mut buf = Vec::new(); + show_timeline(&mut buf, &path, &default_options()) + .await + .unwrap(); + + assert!( + !std::path::Path::new(&format!("{path}-wal")).exists(), + "timeline reopen must not leave a WAL sidecar" + ); + assert!( + !std::path::Path::new(&format!("{path}-shm")).exists(), + "timeline reopen must not leave an SHM sidecar" + ); + } + + #[test] + fn format_timestamp_renders_unix_seconds() { + assert_eq!(format_timestamp(1_600_000_000), "2020-09-13 12:26:40"); + } + + #[tokio::test] + async fn test_timeline_started_at_units_consistent() { + let (agentfs, path, _file) = create_test_agentfs().await; + // tool_calls stores milliseconds; both display forms must expose the + // same second-granularity value. + agentfs + .tools + .record( + "clock", + 1_600_000_000_000, + 1_600_000_000_500, + None, + None, + None, + ) + .await + .unwrap(); + + let mut buf = Vec::new(); + show_timeline(&mut buf, &path, &default_options()) + .await + .unwrap(); + let table = String::from_utf8(buf).unwrap(); + assert!( + table.contains("2020-09-13 12:26:40"), + "table must render millisecond-stored started_at as a date: {table}" + ); + + let mut buf = Vec::new(); + let options = TimelineOptions { + format: "json".to_string(), + ..default_options() + }; + show_timeline(&mut buf, &path, &options).await.unwrap(); + let calls: Vec = serde_json::from_slice(&buf).unwrap(); + assert_eq!(calls[0]["started_at"], 1_600_000_000); + assert_eq!(calls[0]["completed_at"], 1_600_000_000); + assert_eq!(calls[0]["duration_ms"], 500); + } + #[tokio::test] async fn test_timeline_truncate_long_names() { let (agentfs, path, _file) = create_test_agentfs().await; diff --git a/crates/agentfs-cli/src/config.rs b/crates/agentfs-cli/src/config.rs new file mode 100644 index 00000000..df6c8d74 --- /dev/null +++ b/crates/agentfs-cli/src/config.rs @@ -0,0 +1,339 @@ +//! CLI-owned runtime config assembly. + +use agentfs_core::{CoreConfig, EnvReader}; +use std::ffi::OsString; +use std::path::{Path, PathBuf}; +use std::sync::{Mutex, OnceLock}; + +const CLONE_TIMINGS_ENV: &str = "AGENTFS_CLONE_TIMINGS"; +const SHELL_ENV: &str = "SHELL"; +const TURSO_AUTH_TOKEN_ENV: &str = "TURSO_DB_AUTH_TOKEN"; + +#[cfg(target_os = "linux")] +const FUSE_WRITEBACK_ENV: &str = "AGENTFS_FUSE_WRITEBACK"; + +pub(crate) const DEFAULT_CLONE_TIMINGS_ENABLED: bool = false; + +pub(crate) fn core_config_from_env() -> CoreConfig { + #[cfg_attr(not(target_os = "linux"), allow(unused_mut))] + let mut config = CoreConfig::from_env(); + + #[cfg(target_os = "linux")] + { + config.batcher.enabled = EnvReader::new().bool(FUSE_WRITEBACK_ENV, config.batcher.enabled); + } + + config +} + +pub(crate) fn clone_timings_enabled() -> bool { + EnvReader::new() + .string(CLONE_TIMINGS_ENV) + .map_or(DEFAULT_CLONE_TIMINGS_ENABLED, |value| value == "1") +} + +pub(crate) fn current_shell_path() -> Option { + EnvReader::new().string(SHELL_ENV) +} + +pub(crate) fn turso_db_auth_token() -> Option { + EnvReader::new().string(TURSO_AUTH_TOKEN_ENV) +} + +// ─── private sort-spill dir ────────────────────────────────────────────────── +// +// turso_core 0.5.3 creates `tursodb-ephemeral-*` sort-spill files in the +// ambient temp dir and never unlinks them (vdbe/execute.rs:10096), so every +// sort-heavy query litters the host. Until the upstream fix lands, the CLI +// points TMPDIR at a per-process directory removed on exit. Child processes +// of `run`/`exec` must NOT inherit that override: the snapshot below is +// restored into their environment by the spawn paths. + +const SPILL_DIR_PREFIX: &str = "agentfs-spill-"; + +struct SpillDir { + owner_pid: u32, + path: PathBuf, +} + +/// The user's TMPDIR as it was before the CLI overrode it (`None` inside the +/// option = TMPDIR was unset). Outer `None` = the override never ran. +static ORIGINAL_TMPDIR: OnceLock> = OnceLock::new(); +static SPILL_DIR: Mutex> = Mutex::new(None); + +/// Point TMPDIR at a fresh per-process spill dir, removed again at process +/// exit (including the `process::exit` child-status passthrough sites, via +/// `atexit`). Called once at CLI startup, before any threads exist. +pub fn init_private_spill_dir() { + let original = std::env::var_os("TMPDIR"); + let _ = ORIGINAL_TMPDIR.set(original); + reap_stale_spill_dirs(&std::env::temp_dir()); + adopt_private_spill_dir(); + + static ATEXIT: std::sync::Once = std::sync::Once::new(); + ATEXIT.call_once(|| { + extern "C" fn cleanup_spill_dir_at_exit() { + remove_spill_dir_if_owner(); + } + // SAFETY: registering a handler that only touches process-global + // statics; atexit itself has no preconditions. + unsafe { + libc::atexit(cleanup_spill_dir_at_exit); + } + }); +} + +/// Give a daemonized child its own spill dir. After `fork()` the child shares +/// the parent's TMPDIR override, but the parent removes that directory when +/// it exits; the daemon must move off it before doing any database work. +pub(crate) fn adopt_private_spill_dir() { + if ORIGINAL_TMPDIR.get().is_none() { + return; + } + let base = original_temp_dir(); + let pid = std::process::id(); + for attempt in 0u32..16 { + let dir = base.join(format!("{SPILL_DIR_PREFIX}{pid}-{attempt}")); + match std::fs::create_dir(&dir) { + Ok(()) => { + std::env::set_var("TMPDIR", &dir); + *SPILL_DIR.lock().unwrap() = Some(SpillDir { + owner_pid: pid, + path: dir, + }); + return; + } + Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => continue, + Err(err) => { + tracing::debug!(%err, "could not create private spill dir; keeping ambient TMPDIR"); + return; + } + } + } +} + +/// The temp dir the user actually configured, independent of the override. +pub(crate) fn original_temp_dir() -> PathBuf { + match ORIGINAL_TMPDIR.get() { + Some(Some(dir)) if !dir.is_empty() => PathBuf::from(dir), + Some(None) => PathBuf::from("/tmp"), + _ => std::env::temp_dir(), + } +} + +/// Restore the user's TMPDIR into a child about to be spawned. +pub(crate) fn restore_original_tmpdir(command: &mut tokio::process::Command) { + match ORIGINAL_TMPDIR.get() { + Some(Some(original)) => { + command.env("TMPDIR", original); + } + Some(None) => { + command.env_remove("TMPDIR"); + } + None => {} + } +} + +/// Restore the user's TMPDIR into this process's own environment. Used by the +/// `run` sandbox child between `fork()` and `execvp()`. +#[cfg(target_os = "linux")] +pub(crate) fn restore_original_tmpdir_env() { + match ORIGINAL_TMPDIR.get() { + Some(Some(original)) => std::env::set_var("TMPDIR", original), + Some(None) => std::env::remove_var("TMPDIR"), + None => {} + } +} + +fn remove_spill_dir_if_owner() { + let Ok(guard) = SPILL_DIR.lock() else { + return; + }; + if let Some(spill) = guard.as_ref() { + // Forked children (sandbox child, daemon parent/child pairs) inherit + // this state; only the process that created the dir removes it. + if spill.owner_pid == std::process::id() { + let _ = std::fs::remove_dir_all(&spill.path); + } + } +} + +/// Best-effort reaping of spill dirs left behind by SIGKILLed processes: +/// the exit handler never ran for them, so their PID-stamped dirs survive. +fn reap_stale_spill_dirs(base: &Path) { + let Ok(entries) = std::fs::read_dir(base) else { + return; + }; + for entry in entries.flatten() { + let name = entry.file_name(); + let Some(pid) = stale_spill_dir_pid(&name.to_string_lossy()) else { + continue; + }; + if pid != std::process::id() && !process_alive(pid) { + let _ = std::fs::remove_dir_all(entry.path()); + } + } +} + +/// Parse the owner PID out of a `agentfs-spill--` directory name. +fn stale_spill_dir_pid(name: &str) -> Option { + let rest = name.strip_prefix(SPILL_DIR_PREFIX)?; + let (pid, suffix) = rest.split_once('-')?; + if suffix.is_empty() || !suffix.bytes().all(|b| b.is_ascii_digit()) { + return None; + } + pid.parse().ok() +} + +fn process_alive(pid: u32) -> bool { + // SAFETY: kill with signal 0 only performs the existence/permission check. + let result = unsafe { libc::kill(pid as libc::pid_t, 0) }; + result == 0 || std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Mutex; + + static ENV_LOCK: Mutex<()> = Mutex::new(()); + + const CONFIG_ENV_KEYS: &[&str] = &[CLONE_TIMINGS_ENV, SHELL_ENV, TURSO_AUTH_TOKEN_ENV]; + + struct EnvSnapshot { + values: Vec<(&'static str, Option)>, + } + + impl EnvSnapshot { + fn capture(keys: &[&'static str]) -> Self { + let values = keys + .iter() + .map(|key| (*key, std::env::var(key).ok())) + .collect(); + for key in keys { + std::env::remove_var(key); + } + Self { values } + } + } + + impl Drop for EnvSnapshot { + fn drop(&mut self) { + for (key, value) in &self.values { + match value { + Some(value) => std::env::set_var(key, value), + None => std::env::remove_var(key), + } + } + } + } + + #[test] + fn turso_db_auth_token_reads_env_at_cli_edge() { + let _lock = ENV_LOCK.lock().unwrap(); + let _snapshot = EnvSnapshot::capture(CONFIG_ENV_KEYS); + + assert_eq!(turso_db_auth_token(), None); + + std::env::set_var(TURSO_AUTH_TOKEN_ENV, "test-token"); + assert_eq!(turso_db_auth_token().as_deref(), Some("test-token")); + } + + #[test] + fn clone_timings_enabled_reads_explicit_one_only() { + let _lock = ENV_LOCK.lock().unwrap(); + let _snapshot = EnvSnapshot::capture(CONFIG_ENV_KEYS); + + assert!(!clone_timings_enabled()); + + std::env::set_var(CLONE_TIMINGS_ENV, "1"); + assert!(clone_timings_enabled()); + + std::env::set_var(CLONE_TIMINGS_ENV, "true"); + assert!(!clone_timings_enabled()); + } + + #[test] + fn current_shell_path_reads_env_at_cli_edge() { + let _lock = ENV_LOCK.lock().unwrap(); + let _snapshot = EnvSnapshot::capture(CONFIG_ENV_KEYS); + + assert_eq!(current_shell_path(), None); + + std::env::set_var(SHELL_ENV, "/bin/test-shell"); + assert_eq!(current_shell_path().as_deref(), Some("/bin/test-shell")); + } + + #[test] + fn stale_spill_dir_pid_parses_only_generated_names() { + assert_eq!(stale_spill_dir_pid("agentfs-spill-1234-0"), Some(1234)); + assert_eq!(stale_spill_dir_pid("agentfs-spill-1234-15"), Some(1234)); + assert_eq!(stale_spill_dir_pid("agentfs-spill-1234"), None); + assert_eq!(stale_spill_dir_pid("agentfs-spill-abc-0"), None); + assert_eq!(stale_spill_dir_pid("agentfs-spill-1234-x"), None); + assert_eq!(stale_spill_dir_pid("agentfs-spill--0"), None); + assert_eq!(stale_spill_dir_pid("tursodb-ephemeral-1"), None); + } + + #[test] + fn reap_stale_spill_dirs_removes_only_dead_owners() { + let base = tempfile::tempdir().unwrap(); + // Beyond the kernel's maximum pid_max (4194304), so guaranteed dead. + let dead = base.path().join(format!("{SPILL_DIR_PREFIX}99999999-0")); + let alive = base + .path() + .join(format!("{SPILL_DIR_PREFIX}{}-0", std::process::id())); + let unrelated = base.path().join("agentfs-spill-notes"); + std::fs::create_dir(&dead).unwrap(); + std::fs::create_dir(&alive).unwrap(); + std::fs::create_dir(&unrelated).unwrap(); + + reap_stale_spill_dirs(base.path()); + + assert!(!dead.exists(), "dead-owner spill dir must be reaped"); + assert!(alive.exists(), "live-owner spill dir must survive"); + assert!(unrelated.exists(), "non-matching names must survive"); + } + + #[test] + fn private_spill_dir_redirects_temp_dir_and_children_keep_the_original() { + let _lock = ENV_LOCK.lock().unwrap(); + let ambient = std::env::var_os("TMPDIR"); + + init_private_spill_dir(); + + let spill = PathBuf::from(std::env::var_os("TMPDIR").expect("TMPDIR overridden")); + assert!( + spill + .file_name() + .unwrap() + .to_string_lossy() + .starts_with(SPILL_DIR_PREFIX), + "override points at a spill dir, got {}", + spill.display() + ); + assert!(spill.is_dir(), "spill dir exists"); + assert_eq!( + std::env::temp_dir(), + spill, + "temp_dir() follows the override" + ); + + let mut cmd = tokio::process::Command::new("true"); + restore_original_tmpdir(&mut cmd); + let configured: Vec<_> = cmd.as_std().get_envs().collect(); + let expected = ORIGINAL_TMPDIR + .get() + .expect("snapshot latched by init") + .as_deref(); + assert!( + configured.contains(&(std::ffi::OsStr::new("TMPDIR"), expected)), + "child command must see the pre-override TMPDIR ({expected:?}), got {configured:?}" + ); + + match ambient { + Some(value) => std::env::set_var("TMPDIR", value), + None => std::env::remove_var("TMPDIR"), + } + } +} diff --git a/crates/agentfs-cli/src/docs.rs b/crates/agentfs-cli/src/docs.rs new file mode 100644 index 00000000..9cab7657 --- /dev/null +++ b/crates/agentfs-cli/src/docs.rs @@ -0,0 +1,336 @@ +//! Generated clap <-> MANUAL command-reference parity. +//! +//! The command/option sections of docs/MANUAL.md are rendered from the clap +//! command tree in `opts`, so the manual cannot drift from `agentfs --help`. +//! The checked-in manual is generated from the Linux CLI surface (the +//! first-tier platform); `docs::tests::manual_help_parity` fails when the +//! generated region is stale and rewrites it under `AGENTFS_UPDATE_MANUAL=1`. + +use clap::{ArgAction, CommandFactory}; + +use crate::opts::Args; + +#[cfg(all(test, target_os = "linux"))] +const MANUAL_REGEN_ENV: &str = "AGENTFS_UPDATE_MANUAL"; +const MANUAL_REGEN_COMMAND: &str = "AGENTFS_UPDATE_MANUAL=1 cargo +nightly test -p agentfs-cli --lib docs::tests::manual_help_parity -- --exact"; +const GENERATED_BEGIN: &str = ""; +const GENERATED_END: &str = ""; + +/// Render the generated command-reference region of docs/MANUAL.md, +/// markers included. +pub fn generated_manual_commands() -> String { + let cmd = Args::command(); + let mut out = String::new(); + out.push_str(GENERATED_BEGIN); + out.push('\n'); + out.push_str("\n\n"); + out.push_str("## Commands\n\n"); + out.push_str( + "Every section below is generated from the clap definitions the binary \ + actually parses; `agentfs --help` and this reference cannot \ + disagree.\n", + ); + for sub in visible_subcommands(&cmd) { + render_command(&mut out, sub, &CommandContext::root(), 3); + } + out.push('\n'); + out.push_str(GENERATED_END); + out.push('\n'); + out +} + +fn visible_subcommands(cmd: &clap::Command) -> impl Iterator { + cmd.get_subcommands() + .filter(|sub| sub.get_name() != "help" && !sub.is_hide_set()) +} + +/// Heading path plus the invocation prefix (which, unlike the heading, +/// carries the parent commands' positional arguments). +struct CommandContext { + path: String, + invocation: String, +} + +impl CommandContext { + fn root() -> Self { + Self { + path: "agentfs".to_string(), + invocation: "agentfs".to_string(), + } + } + + fn child(&self, cmd: &clap::Command) -> Self { + let path = format!("{} {}", self.path, cmd.get_name()); + let mut invocation = format!("{} {}", self.invocation, cmd.get_name()); + for arg in cmd.get_arguments() { + if arg.is_positional() && !arg.is_hide_set() { + invocation.push(' '); + invocation.push_str(&positional_token(arg)); + } + } + Self { path, invocation } + } +} + +fn render_command(out: &mut String, cmd: &clap::Command, parent: &CommandContext, depth: usize) { + let context = parent.child(cmd); + + out.push('\n'); + for _ in 0..depth { + out.push('#'); + } + out.push_str(&format!(" {}\n", context.path)); + + if let Some(about) = cmd.get_long_about().or_else(|| cmd.get_about()) { + out.push_str(&format!("\n{}\n", about.to_string().trim_end())); + } + + out.push_str(&format!( + "\n```\n{}\n```\n", + synopsis(cmd, &parent.invocation) + )); + + let positionals: Vec<&clap::Arg> = cmd + .get_arguments() + .filter(|arg| arg.is_positional() && !arg.is_hide_set()) + .collect(); + if !positionals.is_empty() { + out.push_str("\n**Arguments:**\n\n"); + for arg in positionals { + out.push_str(&format!( + "- `{}`{}\n", + positional_token(arg), + arg_details(arg) + )); + } + } + + let options: Vec<&clap::Arg> = cmd + .get_arguments() + .filter(|arg| !arg.is_positional() && !arg.is_hide_set() && !is_builtin(arg)) + .collect(); + if !options.is_empty() { + out.push_str("\n**Options:**\n\n"); + for arg in options { + out.push_str(&format!("- `{}`{}\n", option_token(arg), arg_details(arg))); + } + } + + for sub in visible_subcommands(cmd) { + render_command(out, sub, &context, depth + 1); + } +} + +fn is_builtin(arg: &clap::Arg) -> bool { + matches!(arg.get_id().as_str(), "help" | "version") +} + +fn synopsis(cmd: &clap::Command, parent_invocation: &str) -> String { + let mut parts = vec![format!("{parent_invocation} {}", cmd.get_name())]; + if cmd + .get_arguments() + .any(|arg| !arg.is_positional() && !arg.is_hide_set() && !is_builtin(arg)) + { + parts.push("[OPTIONS]".to_string()); + } + for arg in cmd.get_arguments() { + if arg.is_positional() && !arg.is_hide_set() { + parts.push(positional_token(arg)); + } + } + if cmd.has_subcommands() { + parts.push(if cmd.is_subcommand_required_set() { + "".to_string() + } else { + "[COMMAND]".to_string() + }); + } + parts.join(" ") +} + +fn positional_token(arg: &clap::Arg) -> String { + let name = value_name(arg); + let multiple = arg + .get_num_args() + .map(|range| range.max_values() > 1) + .unwrap_or(false); + let suffix = if multiple { "..." } else { "" }; + if arg.is_required_set() { + format!("<{name}>{suffix}") + } else { + format!("[{name}]{suffix}") + } +} + +fn option_token(arg: &clap::Arg) -> String { + let mut token = String::new(); + if let Some(short) = arg.get_short() { + token.push_str(&format!("-{short}, ")); + } + if let Some(long) = arg.get_long() { + token.push_str(&format!("--{long}")); + } + if takes_value(arg) { + token.push_str(&format!(" <{}>", value_name(arg))); + } + token +} + +fn takes_value(arg: &clap::Arg) -> bool { + !matches!( + arg.get_action(), + ArgAction::SetTrue | ArgAction::SetFalse | ArgAction::Count + ) +} + +fn value_name(arg: &clap::Arg) -> String { + arg.get_value_names() + .and_then(|names| names.first()) + .map(|name| name.to_string()) + .unwrap_or_else(|| arg.get_id().as_str().to_ascii_uppercase()) +} + +fn arg_details(arg: &clap::Arg) -> String { + let mut details = String::new(); + + if let Some(help) = arg.get_long_help().or_else(|| arg.get_help()) { + let help = help.to_string(); + let help = help.trim_end(); + details.push_str(" — "); + // Two-space indentation keeps multi-line help inside the list item. + details.push_str(&help.replace('\n', "\n ")); + } + + let mut meta = Vec::new(); + if takes_value(arg) { + let possible: Vec = arg + .get_possible_values() + .iter() + .filter(|value| !value.is_hide_set()) + .map(|value| value.get_name().to_string()) + .collect(); + if !possible.is_empty() { + meta.push(format!("possible values: {}", possible.join(", "))); + } + let defaults: Vec = arg + .get_default_values() + .iter() + .map(|value| value.to_string_lossy().into_owned()) + .collect(); + if !defaults.is_empty() { + meta.push(format!("default: {}", defaults.join(", "))); + } + } + if let Some(env) = arg.get_env() { + meta.push(format!("env: {}", env.to_string_lossy())); + } + if !meta.is_empty() { + details.push_str(&format!(" [{}]", meta.join("; "))); + } + details +} + +#[cfg(test)] +mod tests { + use super::*; + #[cfg(target_os = "linux")] + use std::path::{Path, PathBuf}; + + // The checked-in manual is generated from the Linux (first-tier) surface; + // platform-dependent defaults such as --backend render differently on + // macOS, so parity is asserted only where the doc is generated. + #[cfg(target_os = "linux")] + #[test] + fn manual_help_parity() { + let manual_path = repo_root().join("docs").join("MANUAL.md"); + let manual = std::fs::read_to_string(&manual_path) + .unwrap_or_else(|err| panic!("failed to read {}: {err}", manual_path.display())); + let expected = generated_manual_commands(); + + let begin = manual.find(GENERATED_BEGIN).unwrap_or_else(|| { + panic!( + "{} is missing the `{GENERATED_BEGIN}` marker; regenerate with:\n {}", + manual_path.display(), + MANUAL_REGEN_COMMAND + ) + }); + let end_marker = format!("{GENERATED_END}\n"); + let end = manual[begin..] + .find(&end_marker) + .map(|offset| begin + offset + end_marker.len()) + .unwrap_or_else(|| { + panic!( + "{} is missing the `{GENERATED_END}` marker after the begin marker", + manual_path.display() + ) + }); + + if std::env::var_os(MANUAL_REGEN_ENV).is_some() { + let updated = format!("{}{}{}", &manual[..begin], expected, &manual[end..]); + std::fs::write(&manual_path, updated) + .unwrap_or_else(|err| panic!("failed to rewrite {}: {err}", manual_path.display())); + } + + let actual = std::fs::read_to_string(&manual_path) + .unwrap_or_else(|err| panic!("failed to read {}: {err}", manual_path.display())); + let begin = actual + .find(GENERATED_BEGIN) + .expect("begin marker survives regeneration"); + let end = actual[begin..] + .find(&end_marker) + .map(|offset| begin + offset + end_marker.len()) + .expect("end marker survives regeneration"); + assert_eq!( + &actual[begin..end], + expected, + "{} command reference is stale; regenerate with:\n {}", + manual_path.display(), + MANUAL_REGEN_COMMAND + ); + } + + #[test] + fn generated_manual_documents_regeneration_command() { + let generated = generated_manual_commands(); + assert!( + generated.contains(MANUAL_REGEN_COMMAND), + "generated command reference must document the one-command regeneration flow" + ); + } + + #[test] + fn generated_manual_covers_every_clap_command_path() { + let generated = generated_manual_commands(); + let cmd = Args::command(); + let mut missing = Vec::new(); + collect_paths(&cmd, "agentfs", &mut |path| { + let heading = format!(" {path}\n"); + if !generated.contains(&heading) { + missing.push(path.to_string()); + } + }); + assert!( + missing.is_empty(), + "generated command reference is missing sections for: {missing:?}" + ); + } + + fn collect_paths(cmd: &clap::Command, parent: &str, visit: &mut impl FnMut(&str)) { + for sub in visible_subcommands(cmd) { + let path = format!("{parent} {}", sub.get_name()); + visit(&path); + collect_paths(sub, &path, visit); + } + } + + #[cfg(target_os = "linux")] + fn repo_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .expect("agentfs-cli crate should live two levels below repo root") + .to_path_buf() + } +} diff --git a/crates/agentfs-cli/src/knobs.rs b/crates/agentfs-cli/src/knobs.rs new file mode 100644 index 00000000..d3882664 --- /dev/null +++ b/crates/agentfs-cli/src/knobs.rs @@ -0,0 +1,1036 @@ +//! Canonical runtime knob declarations and KNOBS.md generation. +//! +//! This is the CLI-edge home for the single knob ledger. It covers the typed +//! core config, the typed FUSE adapter config, CLI env-backed options, and the +//! first-class partial-origin CLI policy flags. + +use agentfs_core::{ + CoreConfig, DEFAULT_PARTIAL_ORIGIN_THRESHOLD_BYTES, DEFAULT_WRITE_BATCH_BYTES, + DEFAULT_WRITE_BATCH_GLOBAL_BYTES, DEFAULT_WRITE_BATCH_MS, DEFAULT_WRITE_BATCH_TXN_BYTES, + DEFAULT_WRITE_BATCH_TXN_INODES, +}; + +use agentfs_core::telemetry::DEFAULT_PROFILE_ENABLED; + +use crate::config::DEFAULT_CLONE_TIMINGS_ENABLED; + +// The runtime FUSE config lives in the sealed agentfs-fuse crate while the +// knob ledger lives at the CLI edge. Keep these defaults byte-for-byte aligned +// with agentfs_fuse::adapter::config; the scan roots below cover every +// workspace crate so undeclared runtime env reads cannot bypass the ledger. +#[cfg(target_os = "linux")] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +struct DefaultToken(&'static str); + +#[cfg(target_os = "linux")] +impl DefaultToken { + const fn as_str(self) -> &'static str { + self.0 + } +} + +#[cfg(target_os = "linux")] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +struct UringConfig { + enabled: bool, + spin_us: u64, +} + +#[cfg(target_os = "linux")] +impl Default for UringConfig { + fn default() -> Self { + Self { + enabled: true, + spin_us: 0, + } + } +} + +#[cfg(target_os = "linux")] +const DEFAULT_AUTO_PERCENT: u8 = 50; +#[cfg(target_os = "linux")] +const DEFAULT_DRAIN_ON_FORGET: bool = false; +#[cfg(target_os = "linux")] +const DEFAULT_DRAIN_ON_RELEASE: bool = false; +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_CACHE_DIR: bool = true; +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_FLUSH_INVAL: bool = false; +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_KEEPCACHE: bool = true; +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_NEG_TTL_MS: u64 = 1000; +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_NOFLUSH: bool = true; +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_NOOPEN: bool = true; +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_POSITIVE_TTL_MS: u64 = 10_000; +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_QUEUE: DefaultToken = DefaultToken("derived"); +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_READDIRPLUS: DefaultToken = DefaultToken("always"); +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_SELF_INVAL: bool = false; +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_STICKY_KEEPCACHE_DROP: bool = false; +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_SYNC_INVAL: bool = false; +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_WORKERS: DefaultToken = DefaultToken("auto"); +#[cfg(target_os = "linux")] +const DEFAULT_FUSE_WRITEBACK: bool = true; +#[cfg(target_os = "linux")] +const DEFAULT_INO_FILES_CAP: usize = 65_536; +#[cfg(target_os = "linux")] +const DEFAULT_QUEUE_MEMORY_PERCENT: u8 = 25; +#[cfg(target_os = "linux")] +const DEFAULT_URING_DEPTH: usize = 4; + +/// Knob class required by the architecture. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum KnobClass { + ProductConfig, + KillSwitch, + Sunset, +} + +impl KnobClass { + pub const fn as_str(self) -> &'static str { + match self { + Self::ProductConfig => "product-config", + Self::KillSwitch => "kill-switch", + Self::Sunset => "sunset", + } + } +} + +/// Code-backed default renderer for generated docs. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum DefaultValue { + Literal(&'static str), + Unset, + Removed, + CloneTimingsEnabled, + ProfileEnabled, + CoreOverlayReads, + CoreKeepcacheDelta, + CoreDrainOnSetattr, + PartialOriginMode, + WriteBatchMs, + WriteBatchBytes, + WriteBatchGlobalBytes, + WriteBatchTxnInodes, + WriteBatchTxnBytes, + PartialOriginThresholdBytes, + #[cfg(target_os = "linux")] + FuseWorkers, + #[cfg(target_os = "linux")] + FuseQueue, + #[cfg(target_os = "linux")] + FuseWriteback, + #[cfg(target_os = "linux")] + FuseKeepcache, + #[cfg(target_os = "linux")] + FuseReaddirPlus, + #[cfg(target_os = "linux")] + FuseSyncInval, + #[cfg(target_os = "linux")] + FuseSelfInval, + #[cfg(target_os = "linux")] + FuseDrainOnRelease, + #[cfg(target_os = "linux")] + FuseDrainOnForget, + #[cfg(target_os = "linux")] + FuseFlushInval, + #[cfg(target_os = "linux")] + FuseNoflush, + #[cfg(target_os = "linux")] + FuseNoopen, + #[cfg(target_os = "linux")] + FuseCacheDir, + #[cfg(target_os = "linux")] + FuseStickyKeepcacheDrop, + #[cfg(target_os = "linux")] + FuseUringEnabled, + #[cfg(target_os = "linux")] + FuseUringSpinUs, + #[cfg(target_os = "linux")] + FusePositiveTtlMs, + #[cfg(target_os = "linux")] + FuseNegativeTtlMs, + #[cfg(target_os = "linux")] + FuseAutoPercent, + #[cfg(target_os = "linux")] + FuseQueueMemoryPercent, + #[cfg(target_os = "linux")] + FuseInoFilesCap, + #[cfg(target_os = "linux")] + FuseUringDepth, +} + +impl DefaultValue { + pub fn render(self) -> String { + match self { + Self::Literal(value) => value.to_string(), + Self::Unset => "unset".to_string(), + Self::Removed => "removed".to_string(), + Self::CloneTimingsEnabled => render_bool(DEFAULT_CLONE_TIMINGS_ENABLED).to_string(), + Self::ProfileEnabled => render_bool(DEFAULT_PROFILE_ENABLED).to_string(), + Self::CoreOverlayReads => render_bool(CoreConfig::default().overlay_reads).to_string(), + Self::CoreKeepcacheDelta => { + render_bool(CoreConfig::default().keepcache_delta).to_string() + } + Self::CoreDrainOnSetattr => { + render_bool(CoreConfig::default().drain_on_setattr).to_string() + } + Self::PartialOriginMode => { + partial_origin_mode_as_str(CoreConfig::default().partial_origin.mode).to_string() + } + Self::WriteBatchMs => DEFAULT_WRITE_BATCH_MS.to_string(), + Self::WriteBatchBytes => DEFAULT_WRITE_BATCH_BYTES.to_string(), + Self::WriteBatchGlobalBytes => DEFAULT_WRITE_BATCH_GLOBAL_BYTES.to_string(), + Self::WriteBatchTxnInodes => DEFAULT_WRITE_BATCH_TXN_INODES.to_string(), + Self::WriteBatchTxnBytes => DEFAULT_WRITE_BATCH_TXN_BYTES.to_string(), + Self::PartialOriginThresholdBytes => DEFAULT_PARTIAL_ORIGIN_THRESHOLD_BYTES.to_string(), + #[cfg(target_os = "linux")] + Self::FuseWorkers => DEFAULT_FUSE_WORKERS.as_str().to_string(), + #[cfg(target_os = "linux")] + Self::FuseQueue => DEFAULT_FUSE_QUEUE.as_str().to_string(), + #[cfg(target_os = "linux")] + Self::FuseWriteback => render_bool(DEFAULT_FUSE_WRITEBACK).to_string(), + #[cfg(target_os = "linux")] + Self::FuseKeepcache => render_bool(DEFAULT_FUSE_KEEPCACHE).to_string(), + #[cfg(target_os = "linux")] + Self::FuseReaddirPlus => DEFAULT_FUSE_READDIRPLUS.as_str().to_string(), + #[cfg(target_os = "linux")] + Self::FuseSyncInval => render_bool(DEFAULT_FUSE_SYNC_INVAL).to_string(), + #[cfg(target_os = "linux")] + Self::FuseSelfInval => render_bool(DEFAULT_FUSE_SELF_INVAL).to_string(), + #[cfg(target_os = "linux")] + Self::FuseDrainOnRelease => render_bool(DEFAULT_DRAIN_ON_RELEASE).to_string(), + #[cfg(target_os = "linux")] + Self::FuseDrainOnForget => render_bool(DEFAULT_DRAIN_ON_FORGET).to_string(), + #[cfg(target_os = "linux")] + Self::FuseFlushInval => render_bool(DEFAULT_FUSE_FLUSH_INVAL).to_string(), + #[cfg(target_os = "linux")] + Self::FuseNoflush => render_bool(DEFAULT_FUSE_NOFLUSH).to_string(), + #[cfg(target_os = "linux")] + Self::FuseNoopen => render_bool(DEFAULT_FUSE_NOOPEN).to_string(), + #[cfg(target_os = "linux")] + Self::FuseCacheDir => render_bool(DEFAULT_FUSE_CACHE_DIR).to_string(), + #[cfg(target_os = "linux")] + Self::FuseStickyKeepcacheDrop => { + render_bool(DEFAULT_FUSE_STICKY_KEEPCACHE_DROP).to_string() + } + #[cfg(target_os = "linux")] + Self::FuseUringEnabled => render_bool(UringConfig::default().enabled).to_string(), + #[cfg(target_os = "linux")] + Self::FuseUringSpinUs => UringConfig::default().spin_us.to_string(), + #[cfg(target_os = "linux")] + Self::FusePositiveTtlMs => DEFAULT_FUSE_POSITIVE_TTL_MS.to_string(), + #[cfg(target_os = "linux")] + Self::FuseNegativeTtlMs => DEFAULT_FUSE_NEG_TTL_MS.to_string(), + #[cfg(target_os = "linux")] + Self::FuseAutoPercent => DEFAULT_AUTO_PERCENT.to_string(), + #[cfg(target_os = "linux")] + Self::FuseQueueMemoryPercent => DEFAULT_QUEUE_MEMORY_PERCENT.to_string(), + #[cfg(target_os = "linux")] + Self::FuseInoFilesCap => DEFAULT_INO_FILES_CAP.to_string(), + #[cfg(target_os = "linux")] + Self::FuseUringDepth => DEFAULT_URING_DEPTH.to_string(), + } + } +} + +fn render_bool(value: bool) -> &'static str { + if value { + "true" + } else { + "false" + } +} + +fn partial_origin_mode_as_str(mode: agentfs_core::PartialOriginMode) -> &'static str { + match mode { + agentfs_core::PartialOriginMode::Off => "off", + agentfs_core::PartialOriginMode::On => "on", + agentfs_core::PartialOriginMode::Auto => "auto", + } +} + +/// One row in the canonical knob ledger. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct Knob { + pub name: &'static str, + pub surface: &'static str, + pub class: KnobClass, + pub default: DefaultValue, + pub owner: &'static str, + pub description: &'static str, + pub removal_criteria: &'static str, + pub gate: &'static str, +} + +impl Knob { + const fn product( + name: &'static str, + surface: &'static str, + default: DefaultValue, + owner: &'static str, + description: &'static str, + gate: &'static str, + ) -> Self { + Self { + name, + surface, + class: KnobClass::ProductConfig, + default, + owner, + description, + removal_criteria: "n/a", + gate, + } + } + + #[cfg(target_os = "linux")] + const fn kill_switch( + name: &'static str, + default: DefaultValue, + owner: &'static str, + description: &'static str, + gate: &'static str, + ) -> Self { + Self { + name, + surface: "env", + class: KnobClass::KillSwitch, + default, + owner, + description, + removal_criteria: + "Retire only after N=2 consecutive milestones with both FUSE transport legs green and the named off-leg gate still passing.", + gate, + } + } + + const fn sunset( + name: &'static str, + surface: &'static str, + default: DefaultValue, + owner: &'static str, + description: &'static str, + removal_criteria: &'static str, + gate: &'static str, + ) -> Self { + Self { + name, + surface, + class: KnobClass::Sunset, + default, + owner, + description, + removal_criteria, + gate, + } + } +} + +const CORE_OWNER: &str = "agentfs-core config"; +#[cfg(target_os = "linux")] +const FUSE_OWNER: &str = "agentfs FUSE config"; +const CLI_OWNER: &str = "agentfs CLI edge"; +#[cfg(all(test, target_os = "linux"))] +const KNOBS_DOC_REGEN_ENV: &str = "AGENTFS_UPDATE_KNOBS"; +const KNOBS_DOC_REGEN_COMMAND: &str = "AGENTFS_UPDATE_KNOBS=1 cargo +nightly test -p agentfs-cli --lib knobs::tests::generated_knobs_doc_matches_declarations -- --exact"; + +#[cfg(target_os = "linux")] +const LINUX_FUSE_KNOBS: &[Knob] = &[ + Knob::product( + "AGENTFS_FUSE_WORKERS", + "env", + DefaultValue::FuseWorkers, + FUSE_OWNER, + "FUSE request dispatch mode. auto sizes from CPU and memory budgets; serial disables kernel-cache acceleration for safety.", + "fuse_config::tests::workers_config_feeds_dispatch_and_cache_policy", + ), + Knob::product( + "AGENTFS_FUSE_CPU_PERCENT", + "env", + DefaultValue::FuseAutoPercent, + FUSE_OWNER, + "CPU budget percentage used when AGENTFS_FUSE_WORKERS=auto.", + "knobs::tests::knob_defaults_in_docs_match_runtime_defaults", + ), + Knob::product( + "AGENTFS_FUSE_MEMORY_PERCENT", + "env", + DefaultValue::FuseAutoPercent, + FUSE_OWNER, + "Memory budget percentage used when AGENTFS_FUSE_WORKERS=auto.", + "knobs::tests::knob_defaults_in_docs_match_runtime_defaults", + ), + Knob::product( + "AGENTFS_FUSE_QUEUE", + "env", + DefaultValue::FuseQueue, + FUSE_OWNER, + "FUSE worker request queue capacity; unset derives from worker count and queue memory budget.", + "fuse_config::tests::workers_config_feeds_dispatch_and_cache_policy", + ), + Knob::product( + "AGENTFS_FUSE_QUEUE_MEMORY_PERCENT", + "env", + DefaultValue::FuseQueueMemoryPercent, + FUSE_OWNER, + "Memory budget percentage for derived FUSE queue capacity.", + "knobs::tests::knob_defaults_in_docs_match_runtime_defaults", + ), + Knob::product( + "AGENTFS_FUSE_ENTRY_TTL_MS", + "env", + DefaultValue::FusePositiveTtlMs, + FUSE_OWNER, + "Positive dentry TTL in milliseconds.", + "knobs::tests::knob_defaults_in_docs_match_runtime_defaults", + ), + Knob::product( + "AGENTFS_FUSE_ATTR_TTL_MS", + "env", + DefaultValue::FusePositiveTtlMs, + FUSE_OWNER, + "Attribute TTL in milliseconds.", + "knobs::tests::knob_defaults_in_docs_match_runtime_defaults", + ), + Knob::product( + "AGENTFS_FUSE_NEG_TTL_MS", + "env", + DefaultValue::FuseNegativeTtlMs, + FUSE_OWNER, + "Negative dentry TTL in milliseconds.", + "knobs::tests::knob_defaults_in_docs_match_runtime_defaults", + ), + Knob::product( + "AGENTFS_FUSE_WRITEBACK", + "env", + DefaultValue::FuseWriteback, + FUSE_OWNER, + "Enables FUSE writeback cache and the SDK write batcher when the CLI opens core config.", + "fuse_config::tests::serial_dispatch_disables_kernel_cache_policy", + ), + Knob::product( + "AGENTFS_FUSE_KEEPCACHE", + "env", + DefaultValue::FuseKeepcache, + FUSE_OWNER, + "Allows eligible read-only base files to use FOPEN_KEEP_CACHE.", + "fuse_config::tests::serial_dispatch_disables_kernel_cache_policy", + ), + Knob::product( + "AGENTFS_FUSE_READDIRPLUS", + "env", + DefaultValue::FuseReaddirPlus, + FUSE_OWNER, + "Kernel READDIRPLUS policy.", + "fuse_config::tests::invalid_readdirplus_warns_and_defaults_on", + ), + Knob::product( + "AGENTFS_FUSE_SYNC_INVAL", + "env", + DefaultValue::FuseSyncInval, + FUSE_OWNER, + "Requests synchronous kernel invalidation; serial dispatch keeps deferred invalidation for deadlock safety.", + "fuse_config::tests::serial_dispatch_disables_kernel_cache_policy", + ), + Knob::sunset( + "AGENTFS_FUSE_SELF_INVAL", + "env", + DefaultValue::FuseSelfInval, + FUSE_OWNER, + "Compatibility path that restores notify-on-self-mutation behavior.", + "Remove after adapter cache extraction proves self-invalidation suppression through VAL-FUSE cache and coherence gates.", + "VAL-FUSE-011 and VAL-FUSE-018", + ), + Knob::sunset( + "AGENTFS_DRAIN_ON_RELEASE", + "env", + DefaultValue::FuseDrainOnRelease, + FUSE_OWNER, + "Compatibility path that restores commit-on-close and disables noopen/noflush.", + "Remove after N=2 consecutive milestones with VAL-FUSE-015, noopen, and noflush off-leg gates green.", + "VAL-FUSE-015", + ), + Knob::sunset( + "AGENTFS_DRAIN_ON_FORGET", + "env", + DefaultValue::FuseDrainOnForget, + FUSE_OWNER, + "Compatibility path that restores drain-on-forget.", + "Remove after lifecycle and FORGET-driven cleanup gates are green for N=2 consecutive milestones.", + "VAL-FUSE-003 and VAL-FUSE-004", + ), + Knob::sunset( + "AGENTFS_FUSE_FLUSH_INVAL", + "env", + DefaultValue::FuseFlushInval, + FUSE_OWNER, + "Compatibility path that restores invalidate-on-every-FLUSH.", + "Remove after noflush coherence gates are green for N=2 consecutive milestones.", + "VAL-FUSE-007 and VAL-FUSE-008", + ), + Knob::kill_switch( + "AGENTFS_FUSE_NOFLUSH", + DefaultValue::FuseNoflush, + FUSE_OWNER, + "Disables close-time FLUSH by returning ENOSYS after the kernel has written dirty pages.", + "VAL-FUSE-007 default leg and VAL-FUSE-008 off leg", + ), + Knob::kill_switch( + "AGENTFS_FUSE_NOOPEN", + DefaultValue::FuseNoopen, + FUSE_OWNER, + "Disables per-file OPEN/RELEASE by returning ENOSYS when the kernel supports no-open.", + "VAL-FUSE-003 default leg and VAL-FUSE-004 off leg", + ), + Knob::product( + "AGENTFS_FUSE_INO_FILES_CAP", + "env", + DefaultValue::FuseInoFilesCap, + FUSE_OWNER, + "Soft cap for inode-file cache backing no-open file resolution.", + "scripts/validation/noopen-coherence.py", + ), + Knob::product( + "AGENTFS_FUSE_CACHE_DIR", + "env", + DefaultValue::FuseCacheDir, + FUSE_OWNER, + "Directory-entry cache fast path, effective only when keepcache remains enabled.", + "VAL-FUSE-011 and VAL-FUSE-018", + ), + Knob::sunset( + "AGENTFS_FUSE_STICKY_KEEPCACHE_DROP", + "env", + DefaultValue::FuseStickyKeepcacheDrop, + FUSE_OWNER, + "Compatibility path that restores old sticky keepcache-drop behavior after mutation.", + "Remove after keep-cache fingerprint gates are green for N=2 consecutive milestones.", + "VAL-FUSE-012 and VAL-FUSE-014", + ), + Knob::kill_switch( + "AGENTFS_FUSE_URING", + DefaultValue::FuseUringEnabled, + FUSE_OWNER, + "FUSE-over-io_uring transport attempt. Set false to force the legacy /dev/fuse path.", + "VAL-FUSE-009 uring leg and VAL-FUSE-010 off leg", + ), + Knob::product( + "AGENTFS_FUSE_URING_DEPTH", + "env", + DefaultValue::FuseUringDepth, + FUSE_OWNER, + "io_uring queue depth, bounded by the adapter parser.", + "VAL-FUSE-009", + ), + Knob::sunset( + "AGENTFS_FUSE_URING_SPIN_US", + "env", + DefaultValue::FuseUringSpinUs, + FUSE_OWNER, + "Compatibility tuning for io_uring busy-poll spin before blocking.", + "Remove after uring teardown and performance gates are green for N=2 consecutive milestones.", + "VAL-FUSE-009 and VAL-GATE-004", + ), +]; + +#[cfg(not(target_os = "linux"))] +const LINUX_FUSE_KNOBS: &[Knob] = &[]; + +const ACTIVE_COMMON_KNOBS: &[Knob] = &[ + Knob::product( + "AGENTFS_KEY", + "env or --key", + DefaultValue::Unset, + CLI_OWNER, + "Hex-encoded local encryption key for CLI commands that open a database.", + "opts clap env binding", + ), + Knob::product( + "AGENTFS_CIPHER", + "env or --cipher", + DefaultValue::Unset, + CLI_OWNER, + "Encryption cipher paired with AGENTFS_KEY or --key.", + "opts clap env binding", + ), + Knob::sunset( + "AGENTFS_CLONE_TIMINGS", + "env", + DefaultValue::CloneTimingsEnabled, + CLI_OWNER, + "Ad hoc clone timing printout for local performance investigations.", + "Remove after telemetry registry exposes clone timing through the single report sink.", + "config::clone_timings_enabled", + ), + Knob::product( + "AGENTFS_PROFILE", + "env", + DefaultValue::ProfileEnabled, + CORE_OWNER, + "Enables profiling counters and summaries.", + "VAL-CONF-011 and VAL-CONF-014", + ), + Knob::sunset( + "AGENTFS_OVERLAY_READS", + "env", + DefaultValue::CoreOverlayReads, + CORE_OWNER, + "Tier-4 pending-write read overlay rollback path.", + "Remove after PendingView/stat coherence and overlay read gates are green for N=2 consecutive milestones.", + "VAL-CORE-006 and phase8 smoke", + ), + Knob::product( + "AGENTFS_KEEPCACHE_DELTA", + "env", + DefaultValue::CoreKeepcacheDelta, + CORE_OWNER, + "Allows DB-backed delta files to participate in keep-cache eligibility.", + "VAL-FUSE-014", + ), + Knob::product( + "AGENTFS_BATCH_MS", + "env", + DefaultValue::WriteBatchMs, + CORE_OWNER, + "Write batcher timer window in milliseconds.", + "sdk write-batcher tests", + ), + Knob::product( + "AGENTFS_BATCH_BYTES", + "env", + DefaultValue::WriteBatchBytes, + CORE_OWNER, + "Per-inode pending-byte drain trigger.", + "sdk write-batcher tests", + ), + Knob::product( + "AGENTFS_BATCH_GLOBAL_BYTES", + "env", + DefaultValue::WriteBatchGlobalBytes, + CORE_OWNER, + "Global pending-byte cap across inodes.", + "sdk write-batcher tests", + ), + Knob::product( + "AGENTFS_BATCH_TXN_INODES", + "env", + DefaultValue::WriteBatchTxnInodes, + CORE_OWNER, + "Maximum inodes drained or imported per transaction.", + "sdk write-batcher and import tests", + ), + Knob::product( + "AGENTFS_BATCH_TXN_BYTES", + "env", + DefaultValue::WriteBatchTxnBytes, + CORE_OWNER, + "Maximum bytes drained or imported per transaction.", + "sdk write-batcher and import tests", + ), + Knob::sunset( + "AGENTFS_DRAIN_ON_SETATTR", + "env", + DefaultValue::CoreDrainOnSetattr, + CORE_OWNER, + "Compatibility path that drains pending writes before setattr operations.", + "Remove after PendingView/stat coherence and setattr tests are green for N=2 consecutive milestones.", + "VAL-CORE-006 and VAL-NFS-016", + ), + Knob::product( + "--partial-origin", + "cli flag", + DefaultValue::PartialOriginMode, + CLI_OWNER, + "First-class partial-origin copy-up policy: off, on, or auto.", + "partial_origin::legacy_env_does_not_override_cli_off", + ), + Knob::product( + "--partial-origin-threshold-bytes", + "cli flag", + DefaultValue::PartialOriginThresholdBytes, + CLI_OWNER, + "Size threshold used by --partial-origin auto.", + "opts partial-origin parse tests", + ), +]; + +const DELETED_COMPAT_KNOBS: &[Knob] = &[Knob::sunset( + concat!("AGENTFS_OVERLAY_", "PARTIAL_ORIGIN"), + "deleted env compat", + DefaultValue::Removed, + CORE_OWNER, + "Removed legacy env compatibility path superseded by --partial-origin.", + "Already removed in M3. Do not reintroduce; use --partial-origin or --partial-origin-threshold-bytes.", + "partial_origin::legacy_env_does_not_override_cli_off", +)]; + +/// Active runtime knobs declared in one table. +pub fn active_knobs() -> Vec { + let mut knobs = Vec::with_capacity(ACTIVE_COMMON_KNOBS.len() + LINUX_FUSE_KNOBS.len()); + knobs.extend_from_slice(ACTIVE_COMMON_KNOBS); + knobs.extend_from_slice(LINUX_FUSE_KNOBS); + knobs +} + +/// Deleted compatibility knobs retained only as sunset documentation. +pub fn deleted_compat_knobs() -> &'static [Knob] { + DELETED_COMPAT_KNOBS +} + +/// Generate the checked-in docs/KNOBS.md contents. +pub fn generated_knobs_doc() -> String { + let active = active_knobs(); + let mut out = String::new(); + out.push_str("# AgentFS Runtime Knobs\n\n"); + out.push_str("\n\n"); + out.push_str( + "Every active runtime knob is declared here with an architecture class. Defaults are rendered from the typed config declarations used by the SDK/core and FUSE adapter config modules.\n\n", + ); + out.push_str("## Active knobs\n\n"); + out.push_str( + "| Name | Surface | Class | Default | Owner | Description | Removal criteria | Gate |\n", + ); + out.push_str("|---|---|---|---|---|---|---|---|\n"); + for knob in &active { + push_knob_row(&mut out, knob); + } + + out.push_str("\n## Deleted compatibility knobs\n\n"); + out.push_str( + "| Name | Surface | Class | Default | Owner | Description | Removal criteria | Gate |\n", + ); + out.push_str("|---|---|---|---|---|---|---|---|\n"); + for knob in deleted_compat_knobs() { + push_knob_row(&mut out, knob); + } + out +} + +fn push_knob_row(out: &mut String, knob: &Knob) { + out.push_str("| `"); + out.push_str(knob.name); + out.push_str("` | "); + out.push_str(knob.surface); + out.push_str(" | "); + out.push_str(knob.class.as_str()); + out.push_str(" | `"); + out.push_str(&markdown_escape(&knob.default.render())); + out.push_str("` | "); + out.push_str(&markdown_escape(knob.owner)); + out.push_str(" | "); + out.push_str(&markdown_escape(knob.description)); + out.push_str(" | "); + out.push_str(&markdown_escape(knob.removal_criteria)); + out.push_str(" | "); + out.push_str(&markdown_escape(knob.gate)); + out.push_str(" |\n"); +} + +fn markdown_escape(value: &str) -> String { + value.replace('|', "\\|") +} + +#[cfg(test)] +mod tests { + use super::*; + #[cfg(target_os = "linux")] + use std::collections::BTreeSet; + use std::path::{Path, PathBuf}; + + // The checked-in KNOBS.md is generated from the Linux (first-tier) + // surface; LINUX_FUSE_KNOBS is empty on other targets, so doc parity is + // asserted only where the doc is generated (matching docs.rs + // manual_help_parity). + #[cfg(target_os = "linux")] + #[test] + fn generated_knobs_doc_matches_declarations() { + assert_runtime_env_mentions_are_declared(); + assert_unique_names(active_knobs().iter().chain(deleted_compat_knobs())); + + let docs_path = repo_root().join("docs").join("KNOBS.md"); + let expected = generated_knobs_doc(); + if std::env::var_os(KNOBS_DOC_REGEN_ENV).is_some() { + std::fs::write(&docs_path, &expected) + .unwrap_or_else(|err| panic!("failed to rewrite {}: {err}", docs_path.display())); + } + let actual = std::fs::read_to_string(&docs_path) + .unwrap_or_else(|err| panic!("failed to read {}: {err}", docs_path.display())); + assert_eq!(actual, expected, "{}", stale_knobs_doc_message(&docs_path)); + } + + #[test] + fn generated_knobs_doc_documents_regeneration_command() { + let doc = generated_knobs_doc(); + assert!( + doc.contains(KNOBS_DOC_REGEN_COMMAND), + "generated KNOBS.md must document the one-command regeneration flow" + ); + } + + #[test] + fn stale_knobs_doc_message_names_regeneration_command() { + let message = stale_knobs_doc_message(&repo_root().join("docs").join("KNOBS.md")); + assert!( + message.contains(KNOBS_DOC_REGEN_COMMAND), + "drift failure message must name the regeneration command" + ); + } + + #[test] + fn knob_ledger_defaults_are_code_backed() { + let literal_defaults = active_knobs() + .iter() + .chain(deleted_compat_knobs()) + .filter_map(|knob| match knob.default { + DefaultValue::Literal(value) => Some(format!("{}={value}", knob.name)), + _ => None, + }) + .collect::>(); + assert!( + literal_defaults.is_empty(), + "knob defaults must render from code Default impls or constants, not hand-written literals: {literal_defaults:?}" + ); + } + + #[cfg(target_os = "linux")] + #[test] + fn knob_defaults_in_docs_match_runtime_defaults() { + let active = active_knobs(); + let expected = [ + ("AGENTFS_FUSE_WORKERS", "auto"), + ("AGENTFS_FUSE_QUEUE", "derived"), + ("AGENTFS_FUSE_ENTRY_TTL_MS", "10000"), + ("AGENTFS_FUSE_ATTR_TTL_MS", "10000"), + ("AGENTFS_FUSE_CPU_PERCENT", "50"), + ("AGENTFS_FUSE_MEMORY_PERCENT", "50"), + ("AGENTFS_FUSE_QUEUE_MEMORY_PERCENT", "25"), + ("AGENTFS_BATCH_MS", "5"), + ("AGENTFS_BATCH_BYTES", "4194304"), + ("AGENTFS_BATCH_GLOBAL_BYTES", "67108864"), + ("AGENTFS_BATCH_TXN_INODES", "1024"), + ("AGENTFS_BATCH_TXN_BYTES", "33554432"), + ("AGENTFS_PROFILE", "false"), + ]; + + for (name, default) in expected { + let knob = find_knob(&active, name); + let rendered = knob.default.render(); + println!("{name}={rendered}"); + assert_eq!(rendered, default, "{name} default drifted"); + } + } + + #[cfg(target_os = "linux")] + #[test] + fn all_knobs_have_class_and_sunset_criteria() { + let mut sunset_names = Vec::new(); + for knob in active_knobs().iter().chain(deleted_compat_knobs()) { + assert!( + matches!( + knob.class, + KnobClass::ProductConfig | KnobClass::KillSwitch | KnobClass::Sunset + ), + "{} has an invalid class", + knob.name + ); + if knob.class == KnobClass::Sunset { + assert_ne!( + knob.removal_criteria, "n/a", + "{} is sunset-class but has no removal criteria", + knob.name + ); + sunset_names.push(knob.name); + } + } + sunset_names.sort_unstable(); + println!("sunset knobs: {}", sunset_names.join(", ")); + for required in [ + "AGENTFS_CLONE_TIMINGS", + "AGENTFS_FUSE_SELF_INVAL", + "AGENTFS_DRAIN_ON_RELEASE", + "AGENTFS_DRAIN_ON_FORGET", + "AGENTFS_FUSE_FLUSH_INVAL", + "AGENTFS_FUSE_STICKY_KEEPCACHE_DROP", + "AGENTFS_FUSE_URING_SPIN_US", + "AGENTFS_DRAIN_ON_SETATTR", + "AGENTFS_OVERLAY_READS", + ] { + assert!( + sunset_names.contains(&required), + "missing sunset row for {required}" + ); + } + } + + #[cfg(target_os = "linux")] + #[test] + fn fuse_kill_switches_are_declared_and_gated() { + let active = active_knobs(); + for name in [ + "AGENTFS_FUSE_NOOPEN", + "AGENTFS_FUSE_NOFLUSH", + "AGENTFS_FUSE_URING", + ] { + let knob = find_knob(&active, name); + assert_eq!(knob.class, KnobClass::KillSwitch, "{name} class drifted"); + assert!( + knob.gate.contains("VAL-FUSE"), + "{name} must name its off-leg FUSE gate" + ); + println!("{} => {}", knob.name, knob.gate); + } + } + + #[cfg(target_os = "linux")] + fn find_knob<'a>(knobs: &'a [Knob], name: &str) -> &'a Knob { + knobs + .iter() + .find(|knob| knob.name == name) + .unwrap_or_else(|| panic!("missing knob declaration for {name}")) + } + + fn stale_knobs_doc_message(docs_path: &Path) -> String { + format!( + "{} is stale; regenerate it with:\n {}", + docs_path.display(), + KNOBS_DOC_REGEN_COMMAND + ) + } + + #[cfg(target_os = "linux")] + fn assert_unique_names<'a>(knobs: impl Iterator) { + let mut seen = BTreeSet::new(); + let mut duplicates = Vec::new(); + for knob in knobs { + if !seen.insert(knob.name) { + duplicates.push(knob.name); + } + } + assert!(duplicates.is_empty(), "duplicate knob rows: {duplicates:?}"); + } + + #[cfg(target_os = "linux")] + fn assert_runtime_env_mentions_are_declared() { + let declared = active_knobs() + .iter() + .filter(|knob| { + knob.name + .chars() + .all(|c| c.is_ascii_uppercase() || c == '_') + }) + .map(|knob| knob.name) + .collect::>(); + let mentioned = collect_runtime_env_mentions(); + let missing = mentioned + .difference(&declared) + .copied() + .filter(|name| !ignored_env_token(name)) + .collect::>(); + assert!( + missing.is_empty(), + "runtime env mentions missing knob declarations: {missing:?}" + ); + } + + #[cfg(target_os = "linux")] + fn collect_runtime_env_mentions() -> BTreeSet<&'static str> { + let root = repo_root(); + let mut tokens = BTreeSet::new(); + for source_root in [ + root.join("crates/agentfs-cli/src"), + root.join("crates/agentfs-core/src"), + root.join("crates/agentfs-fuse/src"), + root.join("crates/agentfs-mount/src"), + root.join("crates/agentfs-nfs/src"), + ] { + collect_runtime_env_mentions_from_path(&source_root, &mut tokens); + } + tokens + } + + #[cfg(target_os = "linux")] + fn collect_runtime_env_mentions_from_path(path: &Path, tokens: &mut BTreeSet<&'static str>) { + if path + .components() + .any(|component| component.as_os_str() == "target") + { + return; + } + if path.is_dir() { + for entry in std::fs::read_dir(path).expect("source dir should be readable") { + let entry = entry.expect("source entry should be readable"); + collect_runtime_env_mentions_from_path(&entry.path(), tokens); + } + return; + } + if path.extension().and_then(|ext| ext.to_str()) != Some("rs") { + return; + } + if path.file_name().and_then(|name| name.to_str()) == Some("knobs.rs") { + return; + } + + let contents = std::fs::read_to_string(path).expect("source file should be readable"); + for token in extract_env_tokens(&contents) { + tokens.insert(Box::leak(token.into_boxed_str())); + } + } + + #[cfg(target_os = "linux")] + fn extract_env_tokens(contents: &str) -> BTreeSet { + let mut tokens = BTreeSet::new(); + for prefix in ["AGENTFS_", "TURSO_DB_AUTH_TOKEN"] { + let mut offset = 0; + while let Some(index) = contents[offset..].find(prefix) { + let start = offset + index; + let mut end = start + prefix.len(); + for ch in contents[end..].chars() { + if ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_' { + end += ch.len_utf8(); + } else { + break; + } + } + tokens.insert(contents[start..end].to_string()); + offset = end; + } + } + tokens + } + + #[cfg(target_os = "linux")] + fn ignored_env_token(name: &str) -> bool { + matches!( + name, + "AGENTFS_VERSION" + | "AGENTFS_SCHEMA_VERSION" + | "AGENTFS_SANDBOX" + | "AGENTFS_SESSION" + | "TURSO_DB_AUTH_TOKEN" + // Doc-regeneration test flag (docs.rs), not a runtime knob; + // its knobs.rs sibling is exempt via the knobs.rs file skip. + | "AGENTFS_UPDATE_MANUAL" + ) || name.starts_with("AGENTFS_TEST_") + || name.ends_with('_') + } + + fn repo_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .expect("agentfs-cli crate should live two levels below repo root") + .to_path_buf() + } +} diff --git a/crates/agentfs-cli/src/lib.rs b/crates/agentfs-cli/src/lib.rs new file mode 100644 index 00000000..0dcfe36b --- /dev/null +++ b/crates/agentfs-cli/src/lib.rs @@ -0,0 +1,32 @@ +//! Thin CLI edge for AgentFS. +//! +//! The CLI crate owns argument parsing, user-facing command output, process +//! exit/reporting behavior, and runtime config assembly. Filesystem semantics, +//! transport adapters, and mount lifecycle code live in `agentfs-core`, +//! `agentfs-fuse`, `agentfs-nfs`, and `agentfs-mount` respectively. +//! +//! Owned invariants: +//! +//! - One error reporter: handler results flow back to `main`, which prints +//! the single `Error:` form and exits 1; child-status passthrough is the +//! only sanctioned direct `process::exit`, and every hard-exit site +//! flushes the profile report sink first. +//! - Config at the edge: CLI flags and env are resolved here into typed +//! options before core/transport code runs; every runtime knob is +//! declared in the `knobs` ledger that generates docs/KNOBS.md. +//! - Generated docs parity: the docs/MANUAL.md command reference is +//! rendered from the clap tree by `docs` and pinned by tests, so help +//! output and the manual cannot drift. + +pub mod cmd; +pub mod config; +pub mod docs; +pub mod knobs; +pub mod logging; +pub mod opts; + +pub use cmd::profiling; + +pub fn get_runtime() -> tokio::runtime::Runtime { + tokio::runtime::Runtime::new().expect("Internal error: failed to initialize runtime") +} diff --git a/crates/agentfs-cli/src/logging.rs b/crates/agentfs-cli/src/logging.rs new file mode 100644 index 00000000..d1d66c4c --- /dev/null +++ b/crates/agentfs-cli/src/logging.rs @@ -0,0 +1,87 @@ +//! CLI logging defaults. +//! +//! The binary installs the tracing subscriber, but the default filter must +//! cover every first-party AgentFS crate so warnings are visible even when +//! `RUST_LOG` is unset. + +pub const DEFAULT_ENV_FILTER: &str = concat!( + "agentfs=info,", + "agentfs_cli=info,", + "agentfs_core=info,", + "agentfs_fuse=info,", + "agentfs_nfs=info,", + "agentfs_mount=info" +); + +pub fn default_env_filter() -> tracing_subscriber::EnvFilter { + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| DEFAULT_ENV_FILTER.into()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeSet; + use std::sync::{Arc, Mutex}; + use tracing::{event, Level}; + use tracing_subscriber::layer::{Context, SubscriberExt}; + use tracing_subscriber::registry::LookupSpan; + use tracing_subscriber::{Layer, Registry}; + + #[derive(Clone, Default)] + struct SeenTargets { + targets: Arc>>, + } + + impl Layer for SeenTargets + where + S: tracing::Subscriber, + S: for<'lookup> LookupSpan<'lookup>, + { + fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) { + self.targets + .lock() + .unwrap() + .insert(event.metadata().target().to_string()); + } + } + + #[test] + fn default_env_filter_covers_all_agentfs_crates() { + let previous = std::env::var_os("RUST_LOG"); + std::env::remove_var("RUST_LOG"); + + let seen = SeenTargets::default(); + let targets = [ + "agentfs_core", + "agentfs_fuse", + "agentfs_nfs", + "agentfs_mount", + "agentfs_cli", + ]; + let subscriber = Registry::default() + .with(default_env_filter()) + .with(seen.clone()); + + tracing::dispatcher::with_default(&subscriber.into(), || { + event!(target: "agentfs_core", Level::WARN, "default filter coverage probe"); + event!(target: "agentfs_fuse", Level::WARN, "default filter coverage probe"); + event!(target: "agentfs_nfs", Level::WARN, "default filter coverage probe"); + event!(target: "agentfs_mount", Level::WARN, "default filter coverage probe"); + event!(target: "agentfs_cli", Level::WARN, "default filter coverage probe"); + }); + + match previous { + Some(value) => std::env::set_var("RUST_LOG", value), + None => std::env::remove_var("RUST_LOG"), + } + + let seen = seen.targets.lock().unwrap(); + for target in targets { + assert!( + seen.contains(target), + "default EnvFilter did not enable warning target {target}" + ); + } + } +} diff --git a/crates/agentfs-cli/src/main.rs b/crates/agentfs-cli/src/main.rs new file mode 100644 index 00000000..370c7869 --- /dev/null +++ b/crates/agentfs-cli/src/main.rs @@ -0,0 +1,487 @@ +use agentfs_cli::{ + cmd::{self, completions::handle_completions}, + get_runtime, + logging::default_env_filter, + opts::{Args, Command, FsCommand, PruneCommand, ServeCommand, SyncCommand}, +}; +use clap::{CommandFactory, Parser}; +use clap_complete::CompleteEnv; +use tracing_subscriber::prelude::*; + +/// Parse and validate encryption key and cipher options. +/// Both must be provided together or neither. +fn parse_encryption( + key: Option, + cipher: Option, +) -> anyhow::Result> { + match (key, cipher) { + (Some(key), Some(cipher)) => Ok(Some((key, cipher))), + (Some(_), None) => anyhow::bail!("--cipher is required when using --key"), + (None, Some(_)) => anyhow::bail!("--key is required when using --cipher"), + (None, None) => Ok(None), + } +} + +fn partial_origin_policy( + mode: Option, + threshold_bytes: Option, +) -> Option { + match (mode, threshold_bytes) { + (None, None) => None, + (Some(mode), threshold_bytes) => { + let mut policy = agentfs_core::PartialOriginPolicy::new(mode.into()); + if let Some(threshold_bytes) = threshold_bytes { + policy = policy.with_threshold_bytes(threshold_bytes); + } + Some(policy) + } + (None, Some(threshold_bytes)) => Some( + agentfs_core::PartialOriginPolicy::new(agentfs_core::PartialOriginMode::Auto) + .with_threshold_bytes(threshold_bytes), + ), + } +} + +fn exit_with_error(message: impl std::fmt::Display) -> ! { + eprintln!("Error: {message}"); + exit_with_code(1); +} + +fn exit_with_code(code: i32) -> ! { + agentfs_cli::profiling::emit_cli_report(); + std::process::exit(code); +} + +fn main() { + // Before any threads exist: the TMPDIR override mutates the process + // environment (see config::init_private_spill_dir). + agentfs_cli::config::init_private_spill_dir(); + + let _ = tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with(default_env_filter()) + .try_init(); + + reset_sigpipe(); + + CompleteEnv::with_factory(Args::command).complete(); + let _profile_report = agentfs_cli::profiling::install_cli_sink(); + let args = match Args::try_parse() { + Ok(args) => args, + Err(error) => { + let code = error.exit_code(); + let _ = error.print(); + exit_with_code(code); + } + }; + + // The one CLI error reporter (Display formatting, exit 1). Child-status + // passthrough inside run/exec/init -c is the only other sanctioned exit. + if let Err(e) = dispatch(args) { + exit_with_error(format_args!("{e:#}")); + } +} + +fn dispatch(args: Args) -> anyhow::Result<()> { + match args.command { + Command::Init { + id, + force, + base, + key, + cipher, + command, + backend, + sync, + } => { + let rt = get_runtime(); + let encryption_opts = parse_encryption(key, cipher)? + .map(|(key, cipher)| cmd::init::EncryptionOptions { key, cipher }); + rt.block_on(cmd::init::init_database( + id, + sync, + force, + base, + encryption_opts, + command, + backend, + )) + } + Command::Sync { + id_or_path, + command, + } => { + let rt = get_runtime(); + match command { + SyncCommand::Pull => rt.block_on(cmd::sync::handle_pull_command(id_or_path)), + SyncCommand::Push => rt.block_on(cmd::sync::handle_push_command(id_or_path)), + SyncCommand::Checkpoint => { + rt.block_on(cmd::sync::handle_checkpoint_command(id_or_path)) + } + SyncCommand::Stats => rt.block_on(cmd::sync::handle_stats_command( + &mut std::io::stdout(), + id_or_path, + )), + } + } + Command::Run { + allow, + no_default_allows, + session, + system, + partial_origin, + partial_origin_threshold_bytes, + key, + cipher, + command, + args, + } => { + let encryption = parse_encryption(key, cipher)? + .map(|(hex_key, cipher)| agentfs_core::EncryptionConfig { hex_key, cipher }); + let options = agentfs_cli::opts::RunOptions { + allow, + no_default_allows, + session, + system, + encryption, + partial_origin_policy: partial_origin_policy( + partial_origin, + partial_origin_threshold_bytes, + ), + command: command.unwrap_or_else(default_shell), + args, + }; + // No runtime here: the Linux backend must fork before tokio starts. + cmd::handle_run_command(options) + } + #[cfg(unix)] + Command::Exec { + id_or_path, + command, + args, + backend, + key, + cipher, + } => { + let encryption = parse_encryption(key, cipher)?; + let rt = get_runtime(); + rt.block_on(cmd::exec::handle_exec_command( + id_or_path, command, args, backend, encryption, + )) + } + #[cfg(unix)] + Command::Clone { + id_or_path, + source, + name, + backend, + verify, + } => { + let rt = get_runtime(); + rt.block_on(cmd::clone::handle_clone_command( + id_or_path, source, name, backend, verify, + )) + } + Command::Mount { + id_or_path, + mountpoint, + auto_unmount, + allow_root, + system, + foreground, + uid, + gid, + backend, + partial_origin, + partial_origin_threshold_bytes, + key, + cipher, + } => match (id_or_path, mountpoint) { + (Some(id_or_path), Some(mountpoint)) => cmd::mount(cmd::MountArgs { + id_or_path, + mountpoint, + auto_unmount, + allow_root, + allow_other: system, + foreground, + uid, + gid, + backend, + partial_origin_policy: partial_origin_policy( + partial_origin, + partial_origin_threshold_bytes, + ), + encryption: parse_encryption(key, cipher)?, + }), + (None, None) => { + cmd::mount::list_mounts(&mut std::io::stdout()); + Ok(()) + } + _ => anyhow::bail!("both ID_OR_PATH and MOUNTPOINT are required to mount"), + }, + Command::Diff { id_or_path } => { + let rt = get_runtime(); + rt.block_on(cmd::fs::diff_filesystem(id_or_path)) + } + Command::Timeline { + id_or_path, + limit, + filter, + status, + format, + } => { + let rt = get_runtime(); + let options = cmd::timeline::TimelineOptions { + limit, + filter, + status, + format, + }; + rt.block_on(cmd::timeline::show_timeline( + &mut std::io::stdout(), + &id_or_path, + &options, + )) + } + Command::Fs { + command, + id_or_path, + key, + cipher, + } => { + let encryption = parse_encryption(key, cipher)?; + let rt = get_runtime(); + match command { + FsCommand::Ls { fs_path } => rt.block_on(cmd::fs::ls_filesystem( + &mut std::io::stdout(), + id_or_path, + &fs_path, + encryption.as_ref(), + )), + FsCommand::Cat { file_path } => rt.block_on(cmd::fs::cat_filesystem( + &mut std::io::stdout(), + id_or_path, + &file_path, + encryption.as_ref(), + )), + FsCommand::Write { file_path, content } => rt.block_on(cmd::fs::write_filesystem( + id_or_path, + &file_path, + &content, + encryption.as_ref(), + )), + } + } + Command::Completions { command } => handle_completions(command), + #[cfg(unix)] + Command::Nfs { + id_or_path, + bind, + port, + key, + cipher, + } => { + eprintln!("Warning: `agentfs nfs` is deprecated, use `agentfs serve nfs` instead"); + let encryption = parse_encryption(key, cipher)?; + let rt = get_runtime(); + rt.block_on(cmd::nfs::handle_nfs_command( + id_or_path, bind, port, encryption, + )) + } + Command::McpServer { id_or_path, tools } => { + eprintln!( + "Warning: `agentfs mcp-server` is deprecated, use `agentfs serve mcp` instead" + ); + let rt = get_runtime(); + rt.block_on(cmd::mcp_server::handle_mcp_server_command( + id_or_path, tools, + )) + } + Command::Serve { command } => match command { + #[cfg(unix)] + ServeCommand::Nfs { + id_or_path, + bind, + port, + key, + cipher, + } => { + let encryption = parse_encryption(key, cipher)?; + let rt = get_runtime(); + rt.block_on(cmd::nfs::handle_nfs_command( + id_or_path, bind, port, encryption, + )) + } + ServeCommand::Mcp { id_or_path, tools } => { + let rt = get_runtime(); + rt.block_on(cmd::mcp_server::handle_mcp_server_command( + id_or_path, tools, + )) + } + }, + Command::Ps => cmd::ps::list_ps(&mut std::io::stdout()), + Command::Prune { command } => match command { + PruneCommand::Mounts { force } => cmd::mount::prune_mounts(force), + }, + Command::Integrity { + id_or_path, + json, + require_portable, + check_base, + checkpoint, + key, + cipher, + } => { + let encryption = parse_encryption(key, cipher)?; + let rt = get_runtime(); + rt.block_on(cmd::safety::handle_integrity_command( + &mut std::io::stdout(), + id_or_path, + json, + require_portable, + check_base, + checkpoint, + encryption.as_ref(), + )) + } + Command::Backup { + id_or_path, + target, + verify, + materialize, + key, + cipher, + } => { + let encryption = parse_encryption(key, cipher)?; + let rt = get_runtime(); + rt.block_on(cmd::safety::handle_backup_command( + &mut std::io::stdout(), + id_or_path, + target, + verify, + materialize, + encryption.as_ref(), + )) + } + Command::Materialize { + id_or_path, + output, + verify, + key, + cipher, + } => { + let encryption = parse_encryption(key, cipher)?; + let rt = get_runtime(); + rt.block_on(cmd::safety::handle_materialize_command( + &mut std::io::stdout(), + id_or_path, + output, + verify, + encryption.as_ref(), + )) + } + Command::Migrate { + id_or_path, + dry_run, + copy, + verify, + overwrite_target, + key, + cipher, + } => { + let encryption = parse_encryption(key, cipher)?; + let rt = get_runtime(); + if let Some(target) = copy { + rt.block_on(cmd::migrate::handle_migrate_copy_command( + &mut std::io::stdout(), + id_or_path, + target, + verify, + overwrite_target, + encryption.as_ref(), + )) + } else { + rt.block_on(cmd::migrate::handle_migrate_command( + &mut std::io::stdout(), + id_or_path, + dry_run, + encryption.as_ref(), + )) + } + } + } +} + +/// Reset SIGPIPE to the default behavior (terminate the process) so that +/// piping output to tools like `head` doesn't cause a panic. +#[cfg(unix)] +fn reset_sigpipe() { + unsafe { + libc::signal(libc::SIGPIPE, libc::SIG_DFL); + } +} + +#[cfg(not(unix))] +fn reset_sigpipe() {} + +/// Returns the default shell for the current platform. +/// Linux uses bash, macOS uses zsh. +fn default_shell() -> std::path::PathBuf { + #[cfg(target_os = "macos")] + { + std::path::PathBuf::from("zsh") + } + #[cfg(not(target_os = "macos"))] + { + std::path::PathBuf::from("bash") + } +} + +#[cfg(test)] +mod partial_origin { + use super::partial_origin_policy; + use agentfs_cli::opts::{Args, Command, PartialOriginMode}; + use clap::Parser; + + #[test] + fn legacy_env_does_not_override_cli_off() { + let key = concat!("AGENTFS_OVERLAY_", "PARTIAL_ORIGIN"); + let previous = std::env::var(key).ok(); + std::env::set_var(key, "1"); + + let args = Args::try_parse_from([ + "agentfs", + "run", + "--partial-origin", + "off", + "--", + "sh", + "-c", + "true", + ]) + .expect("run args with --partial-origin off should parse"); + + let (mode, threshold_bytes) = match args.command { + Command::Run { + partial_origin, + partial_origin_threshold_bytes, + .. + } => (partial_origin, partial_origin_threshold_bytes), + other => panic!("expected run command, got {other:?}"), + }; + let policy = partial_origin_policy(mode, threshold_bytes) + .expect("--partial-origin off should resolve an explicit policy"); + + match previous { + Some(value) => std::env::set_var(key, value), + None => std::env::remove_var(key), + } + + eprintln!( + "legacy partial-origin env set; CLI policy resolved to {:?}", + policy.mode + ); + assert_eq!(mode, Some(PartialOriginMode::Off)); + assert_eq!(policy.mode, agentfs_core::PartialOriginMode::Off); + } +} diff --git a/cli/src/opts.rs b/crates/agentfs-cli/src/opts.rs similarity index 83% rename from cli/src/opts.rs rename to crates/agentfs-cli/src/opts.rs index aa5ba2bb..02d5e58b 100644 --- a/cli/src/opts.rs +++ b/crates/agentfs-cli/src/opts.rs @@ -1,5 +1,5 @@ use crate::cmd::completions::Shell; -use agentfs_sdk::agentfs_dir; +use agentfs_core::agentfs_dir; use clap::{Parser, Subcommand}; use clap_complete::{ engine::ValueCompleter, ArgValueCompleter, CompletionCandidate, PathCompleter, @@ -39,6 +39,15 @@ impl std::fmt::Display for MountBackend { } } +impl From for agentfs_mount::Backend { + fn from(value: MountBackend) -> Self { + match value { + MountBackend::Fuse => agentfs_mount::Backend::Fuse, + MountBackend::Nfs => agentfs_mount::Backend::Nfs, + } + } +} + /// Partial-origin copy-up policy. #[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)] pub enum PartialOriginMode { @@ -50,16 +59,41 @@ pub enum PartialOriginMode { Auto, } -impl From for agentfs_sdk::PartialOriginMode { +impl From for agentfs_core::PartialOriginMode { fn from(value: PartialOriginMode) -> Self { match value { - PartialOriginMode::Off => agentfs_sdk::PartialOriginMode::Off, - PartialOriginMode::On => agentfs_sdk::PartialOriginMode::On, - PartialOriginMode::Auto => agentfs_sdk::PartialOriginMode::Auto, + PartialOriginMode::Off => agentfs_core::PartialOriginMode::Off, + PartialOriginMode::On => agentfs_core::PartialOriginMode::On, + PartialOriginMode::Auto => agentfs_core::PartialOriginMode::Auto, } } } +/// Resolved knobs for `agentfs run`. +/// +/// One struct threaded through `cmd/run/*` so adding a run knob touches the +/// clap arm, this struct, and the platform backend that consumes it — not a +/// parallel parameter list in every platform file. +#[derive(Debug)] +pub struct RunOptions { + /// Additional host directories granted read/write access in the sandbox. + pub allow: Vec, + /// Skip the built-in `DEFAULT_ALLOWED_DIRS` grants. + pub no_default_allows: bool, + /// Session identifier for sharing a delta layer across runs. + pub session: Option, + /// Allow other system users to access the mount (FUSE allow_other). + pub system: bool, + /// Delta-layer encryption, already validated at the CLI edge. + pub encryption: Option, + /// Partial-origin copy-up policy resolved from CLI flags. + pub partial_origin_policy: Option, + /// Command to execute inside the sandbox. + pub command: PathBuf, + /// Arguments for the command. + pub args: Vec, +} + #[derive(Parser, Debug)] #[command(name = "agentfs")] #[command(version = env!("AGENTFS_VERSION"))] @@ -85,7 +119,7 @@ pub struct SyncCommandOptions { #[derive(Subcommand, Debug)] pub enum Command { - /// Manage shell completions + /// Manage shell completions (supported shells: bash, zsh, fish, elvish, powershell) Completions { #[command(subcommand)] command: CompletionsCommand, @@ -155,9 +189,12 @@ pub enum Command { /// /// By default, uses FUSE+overlay with Linux user and mount namespaces for isolation. /// The overlay uses the host filesystem as a read-only base and stores - /// all changes in an AgentFS-backed delta layer. + /// all changes in an AgentFS-backed delta layer. On macOS the overlay is + /// mounted over NFS and a generated Seatbelt profile scopes writes to the + /// sandbox and reads to the allowed directories plus required platform + /// paths (see the Sandboxing section of docs/MANUAL.md). Run { - /// Allow write access to additional directories (can be specified multiple times) + /// Allow read/write access to additional directories (can be specified multiple times) #[arg(long = "allow", value_name = "PATH")] allow: Vec, @@ -165,15 +202,6 @@ pub enum Command { #[arg(long = "no-default-allows")] no_default_allows: bool, - /// Use experimental ptrace-based syscall interception sandbox - #[arg(long = "experimental-sandbox")] - experimental_sandbox: bool, - - /// Enable strace-like output for system calls - /// Only used with --experimental-sandbox - #[arg(long = "strace")] - strace: bool, - /// Session identifier for sharing delta layer across multiple runs. /// If not provided, a unique session ID is generated for each run. /// Use the same session ID to share the delta layer between runs. @@ -320,6 +348,15 @@ pub enum Command { /// Size threshold for --partial-origin auto #[arg(long = "partial-origin-threshold-bytes", value_name = "BYTES")] partial_origin_threshold_bytes: Option, + + /// Hex-encoded encryption key for encrypted databases. + #[arg(long, env = "AGENTFS_KEY")] + key: Option, + + /// Cipher algorithm for encryption (required with --key). + /// Options: aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4, aes128gcm, aes256gcm + #[arg(long, env = "AGENTFS_CIPHER")] + cipher: Option, }, /// Show differences between base filesystem and delta (overlay mode only) Diff { @@ -364,6 +401,15 @@ pub enum Command { /// Port to listen on #[arg(long, default_value = "11111")] port: u32, + + /// Hex-encoded encryption key for encrypted databases. + #[arg(long, env = "AGENTFS_KEY")] + key: Option, + + /// Cipher algorithm for encryption (required with --key). + /// Options: aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4, aes128gcm, aes256gcm + #[arg(long, env = "AGENTFS_CIPHER")] + cipher: Option, }, /// Start an MCP server exposing filesystem and KV-store tools @@ -374,8 +420,8 @@ pub enum Command { id_or_path: String, /// Tools to expose (comma-separated). If not provided, all tools are exposed. - /// Available tools: read_file, write_file, readdir, mkdir, rmdir, rm, unlink, - /// copy_file, rename, stat, access, kv_get, kv_set, kv_delete, kv_list + /// Available tools: read_file, write_file, readdir, mkdir, remove, rename, + /// stat, access, kv_get, kv_set, kv_delete, kv_list #[arg(long, value_delimiter = ',')] tools: Option>, }, @@ -410,6 +456,10 @@ pub enum Command { #[arg(long)] check_base: bool, + /// Checkpoint the WAL and remove empty SQLite sidecars after checks pass + #[arg(long)] + checkpoint: bool, + /// Hex-encoded encryption key for encrypted databases #[arg(long)] key: Option, @@ -472,25 +522,29 @@ pub enum Command { id_or_path: String, /// Preview migration without applying changes - #[arg(long)] + #[arg(long, conflicts_with = "copy")] dry_run: bool, - }, - /// Copy a v0.4 database into a v0.5 database - #[command(name = "migrate-v0-5")] - MigrateV0_5 { - /// Source v0.4 database path - source: PathBuf, - /// Target v0.5 database path - target: PathBuf, + /// Copy-migrate into a new database file at this path instead of + /// migrating in place + #[arg(long, value_name = "TARGET")] + copy: Option, - /// Verify migrated state equivalence - #[arg(long)] + /// Verify migrated state equivalence (requires --copy) + #[arg(long, requires = "copy")] verify: bool, - /// Allow replacing an existing target database - #[arg(long)] + /// Allow replacing an existing --copy target database + #[arg(long, requires = "copy")] overwrite_target: bool, + + /// Hex-encoded encryption key for encrypted databases + #[arg(long)] + key: Option, + + /// Encryption cipher (required with --key) + #[arg(long)] + cipher: Option, }, } @@ -545,6 +599,15 @@ pub enum ServeCommand { /// Port to listen on #[arg(long, default_value = "11111")] port: u32, + + /// Hex-encoded encryption key for encrypted databases. + #[arg(long, env = "AGENTFS_KEY")] + key: Option, + + /// Cipher algorithm for encryption (required with --key). + /// Options: aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4, aes128gcm, aes256gcm + #[arg(long, env = "AGENTFS_CIPHER")] + cipher: Option, }, /// Start an MCP server exposing filesystem and KV-store tools @@ -554,8 +617,8 @@ pub enum ServeCommand { id_or_path: String, /// Tools to expose (comma-separated). If not provided, all tools are exposed. - /// Available tools: read_file, write_file, readdir, mkdir, rmdir, rm, unlink, - /// copy_file, rename, stat, access, kv_get, kv_set, kv_delete, kv_list + /// Available tools: read_file, write_file, readdir, mkdir, remove, rename, + /// stat, access, kv_get, kv_set, kv_delete, kv_list #[arg(long, value_delimiter = ',')] tools: Option>, }, diff --git a/cli/tests/.gitignore b/crates/agentfs-cli/tests/.gitignore similarity index 100% rename from cli/tests/.gitignore rename to crates/agentfs-cli/tests/.gitignore diff --git a/crates/agentfs-cli/tests/all.sh b/crates/agentfs-cli/tests/all.sh new file mode 100755 index 00000000..5f045417 --- /dev/null +++ b/crates/agentfs-cli/tests/all.sh @@ -0,0 +1,187 @@ +#!/bin/sh +set -u + +# Set AGENTFS_GATE_FORCE_SKIP= to force a synthetic SKIP without +# running the selected test. This hook exists only to validate SKIP accounting. +# +# Set AGENTFS_GATE_ALLOWED_SKIPS= to name tests whose SKIP +# stays green under AGENTFS_GATE_STRICT=1. This is for runner kernels that +# cannot provide a prerequisite at all (e.g. no FUSE-over-io_uring module +# parameter); every other SKIP remains red on the strict runner. + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" +cd "$CLI_DIR" + +# Suite hygiene contract: every suite that invokes `agentfs run` with the real +# HOME must pass an explicit unique `--session` id and remove exactly the +# ~/.agentfs/run/ dirs it created in its cleanup trap (the user may +# have real sessions there — NEVER sweep ~/.agentfs/run wholesale). Suites +# that override HOME to a temp root are covered by their temp-root removal. + +# Pin TMPDIR to a per-run scratch dir cleaned on exit: turso_core 0.5.3 leaks +# /tmp/tursodb-ephemeral-* sort-spill files (vdbe/execute.rs:10096 never +# unlinks them), so dependency litter must not accumulate on the host. +SUITE_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-all.XXXXXX")" +trap 'rm -rf "$SUITE_TMPDIR"' EXIT INT TERM +TMPDIR="$SUITE_TMPDIR" +TMP="$SUITE_TMPDIR" +TEMP="$SUITE_TMPDIR" +export TMPDIR TMP TEMP + +PASS_COUNT=0 +SKIP_COUNT=0 +DISALLOWED_SKIP_COUNT=0 +FAIL_COUNT=0 +RESULTS="" + +truthy() { + case "${1:-}" in + 1|true|TRUE|yes|YES|on|ON) + return 0 + ;; + *) + return 1 + ;; + esac +} + +skip_allowed() { + allowed_list="${AGENTFS_GATE_ALLOWED_SKIPS:-}" + [ -n "$allowed_list" ] || return 1 + old_ifs="${IFS-}" + IFS=', ' + for allowed in $allowed_list; do + if [ "$allowed" = "$1" ]; then + IFS="$old_ifs" + return 0 + fi + done + IFS="$old_ifs" + return 1 +} + +record_result() { + label="$1" + result="$2" + + case "$result" in + PASS) + PASS_COUNT=$((PASS_COUNT + 1)) + ;; + SKIP) + SKIP_COUNT=$((SKIP_COUNT + 1)) + if skip_allowed "$label"; then + result="SKIP (allowed by AGENTFS_GATE_ALLOWED_SKIPS)" + else + DISALLOWED_SKIP_COUNT=$((DISALLOWED_SKIP_COUNT + 1)) + fi + ;; + FAIL) + FAIL_COUNT=$((FAIL_COUNT + 1)) + ;; + esac + RESULTS="${RESULTS}${label}: ${result} +" + printf 'RESULT %s: %s\n' "$label" "$result" +} + +run_test() { + label="$1" + shift + + printf '\n==> %s\n' "$label" + tmp="$(mktemp "${TMPDIR:-/tmp}/agentfs-all.${label}.XXXXXX")" + + if [ "${AGENTFS_GATE_FORCE_SKIP:-}" = "$label" ] || + [ "${AGENTFS_GATE_FORCE_SKIP:-}" = "all" ]; then + printf 'SKIP: forced by AGENTFS_GATE_FORCE_SKIP=%s\n' "${AGENTFS_GATE_FORCE_SKIP:-}" >"$tmp" + status=0 + else + "$@" >"$tmp" 2>&1 + status=$? + fi + + cat "$tmp" + + if [ "$status" -eq 0 ]; then + if grep -Eq '(^|[[:space:]])SKIP:' "$tmp"; then + record_result "$label" SKIP + else + record_result "$label" PASS + fi + else + record_result "$label" FAIL + fi + + rm -f "$tmp" +} + +run_test "init" "$DIR/test-init.sh" + +# Fast end-to-end pass over the whole user-level command surface (VAL-CLI-024). +run_test "cli-smoke" "$DIR/test-cli-smoke.sh" + +# Syscall tests in two configurations: +# 1. Linux baseline, establishes expected behavior. +run_test "linux-syscalls" "$DIR/test-linux-syscalls.sh" + +# 2. FUSE overlay through agentfs run, tests copy-on-write. +run_test "run-syscalls" "$DIR/test-run-syscalls.sh" + +run_test "run-bash" "$DIR/test-run-bash.sh" +run_test "run-git" "$DIR/test-run-git.sh" +run_test "run-read-scoping" "$DIR/test-run-read-scoping.sh" +run_test "profile-error-summary" "$DIR/test-profile-error-summary.sh" +run_test "ephemeral-sidecar-cleanup" "$DIR/test-ephemeral-sidecar-cleanup.sh" +run_test "teardown-bounded" "$DIR/test-teardown-bounded.sh" +run_test "fuse-sigint-panic-census" "$DIR/test-fuse-sigint-panic-census.sh" +run_test "sigkill-recovery" "$DIR/test-sigkill-recovery.sh" +run_test "signal-teardown" env \ + SIGNAL_TEARDOWN_SIGNAL_DELAY="${SIGNAL_TEARDOWN_SIGNAL_DELAY:-8s}" \ + "$DIR/test-signal-teardown.sh" + +# Corruption/concurrency torture runs both FUSE transport legs. The test prints +# SKIP and exits 0 if Linux user namespace/FUSE prerequisites are unavailable; +# AGENTFS_GATE_STRICT=1 makes such skips fail the gate on the designated runner. +run_test "corruption-torture-legacy" env \ + AGENTFS_FUSE_URING=0 \ + CORRUPTION_TORTURE_WORKERS="${CORRUPTION_TORTURE_WORKERS:-4}" \ + CORRUPTION_TORTURE_ITERATIONS="${CORRUPTION_TORTURE_ITERATIONS:-3}" \ + CORRUPTION_TORTURE_TIMEOUT="${CORRUPTION_TORTURE_TIMEOUT:-120}" \ + CORRUPTION_TORTURE_TEARDOWN_TIMEOUT="${CORRUPTION_TORTURE_TEARDOWN_TIMEOUT:-10}" \ + CORRUPTION_TORTURE_INTEGRITY_INTERVAL="${CORRUPTION_TORTURE_INTEGRITY_INTERVAL:-1}" \ + "$DIR/test-corruption-torture.sh" +run_test "corruption-torture-uring" env \ + AGENTFS_FUSE_URING=1 \ + CORRUPTION_TORTURE_WORKERS="${CORRUPTION_TORTURE_WORKERS:-4}" \ + CORRUPTION_TORTURE_ITERATIONS="${CORRUPTION_TORTURE_ITERATIONS:-3}" \ + CORRUPTION_TORTURE_TIMEOUT="${CORRUPTION_TORTURE_TIMEOUT:-120}" \ + CORRUPTION_TORTURE_TEARDOWN_TIMEOUT="${CORRUPTION_TORTURE_TEARDOWN_TIMEOUT:-10}" \ + CORRUPTION_TORTURE_INTEGRITY_INTERVAL="${CORRUPTION_TORTURE_INTEGRITY_INTERVAL:-1}" \ + "$DIR/test-corruption-torture.sh" + +run_test "mount" "$DIR/test-mount.sh" +run_test "second-opener" "$DIR/test-second-opener.sh" +run_test "migrate-consolidation" "$DIR/test-migrate-consolidation.sh" +run_test "mcp-server" "$DIR/test-mcp-server.sh" +run_test "overlay-whiteout" "$DIR/test-overlay-whiteout.sh" +run_test "overlay-delta-in-base-dir" "$DIR/test-overlay-delta-in-base-dir.sh" +run_test "overlay-base-dir-rename-exdev" "$DIR/test-overlay-base-dir-rename-exdev.sh" +run_test "fuse-cache-invalidation" "$DIR/test-fuse-cache-invalidation.sh" +run_test "symlinks" "$DIR/test-symlinks.sh" + +printf '\nShell gate results:\n%s' "$RESULTS" +printf 'SUMMARY PASS=%s SKIP=%s FAIL=%s\n' "$PASS_COUNT" "$SKIP_COUNT" "$FAIL_COUNT" + +if [ "$FAIL_COUNT" -ne 0 ]; then + exit 1 +fi + +if [ "$DISALLOWED_SKIP_COUNT" -ne 0 ] && truthy "${AGENTFS_GATE_STRICT:-0}"; then + printf 'FAILED: AGENTFS_GATE_STRICT=1 treats SKIP as a gate failure (disallowed skips: %s, AGENTFS_GATE_ALLOWED_SKIPS=%s)\n' \ + "$DISALLOWED_SKIP_COUNT" "${AGENTFS_GATE_ALLOWED_SKIPS:-}" + exit 1 +fi + +exit 0 diff --git a/crates/agentfs-cli/tests/fixtures/migrate/v0_0.db b/crates/agentfs-cli/tests/fixtures/migrate/v0_0.db new file mode 100644 index 00000000..0646ea06 Binary files /dev/null and b/crates/agentfs-cli/tests/fixtures/migrate/v0_0.db differ diff --git a/crates/agentfs-cli/tests/fixtures/migrate/v0_2.db b/crates/agentfs-cli/tests/fixtures/migrate/v0_2.db new file mode 100644 index 00000000..3c4fec2b Binary files /dev/null and b/crates/agentfs-cli/tests/fixtures/migrate/v0_2.db differ diff --git a/crates/agentfs-cli/tests/fixtures/migrate/v0_4-encrypted.db b/crates/agentfs-cli/tests/fixtures/migrate/v0_4-encrypted.db new file mode 100644 index 00000000..909fcd3e Binary files /dev/null and b/crates/agentfs-cli/tests/fixtures/migrate/v0_4-encrypted.db differ diff --git a/crates/agentfs-cli/tests/fixtures/migrate/v0_4.db b/crates/agentfs-cli/tests/fixtures/migrate/v0_4.db new file mode 100644 index 00000000..8b92931a Binary files /dev/null and b/crates/agentfs-cli/tests/fixtures/migrate/v0_4.db differ diff --git a/crates/agentfs-cli/tests/migrate_fixtures.rs b/crates/agentfs-cli/tests/migrate_fixtures.rs new file mode 100644 index 00000000..ace40688 --- /dev/null +++ b/crates/agentfs-cli/tests/migrate_fixtures.rs @@ -0,0 +1,354 @@ +//! Committed old-schema fixture databases for migrate tests and validators. +//! +//! Fixtures live in `tests/fixtures/migrate/` and are exercised by +//! `test-migrate-consolidation.sh` and by validation flows against real old +//! databases (v0.0, v0.2, v0.4, and an encrypted v0.4). +//! +//! Regenerate after a schema-authority or turso format change with: +//! +//! ```sh +//! cargo +nightly test -p agentfs-cli --test migrate_fixtures -- --ignored +//! ``` + +use agentfs_core::schema::{self, SchemaVersion}; +use std::fs; +use std::path::{Path, PathBuf}; +use turso::{Builder, Connection, EncryptionOpts, Value}; + +/// Key/cipher for `v0_4-encrypted.db`; kept in step with +/// `test-migrate-consolidation.sh`. +const ENCRYPTED_FIXTURE_KEY: &str = + "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"; +const ENCRYPTED_FIXTURE_CIPHER: &str = "aes256gcm"; + +const FIXTURES: &[(&str, SchemaVersion, bool)] = &[ + ("v0_0.db", SchemaVersion::V0_0, false), + ("v0_2.db", SchemaVersion::V0_2, false), + ("v0_4.db", SchemaVersion::V0_4, false), + ("v0_4-encrypted.db", SchemaVersion::V0_4, true), +]; + +fn fixtures_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/migrate") +} + +#[tokio::test] +#[ignore = "rewrites the committed fixture databases; run explicitly"] +async fn regenerate_migrate_fixtures() { + let dir = fixtures_dir(); + fs::create_dir_all(&dir).unwrap(); + for (name, version, encrypted) in FIXTURES { + let path = dir.join(name); + for suffix in ["", "-wal", "-shm"] { + let sidecar = PathBuf::from(format!("{}{}", path.display(), suffix)); + match fs::remove_file(&sidecar) { + Ok(()) => {} + Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} + Err(err) => panic!("cannot remove {}: {err}", sidecar.display()), + } + } + create_fixture(&path, *version, *encrypted).await; + assert!(path.is_file()); + println!( + "regenerated {} ({} bytes)", + path.display(), + fs::metadata(&path).unwrap().len() + ); + } +} + +#[tokio::test] +async fn committed_fixtures_detect_as_expected_versions() { + let scratch = tempfile::tempdir().unwrap(); + for (name, version, encrypted) in FIXTURES { + let committed = fixtures_dir().join(name); + assert!( + committed.is_file(), + "missing committed fixture {} (regenerate with --ignored)", + committed.display() + ); + let copy = scratch.path().join(name); + fs::copy(&committed, ©).unwrap(); + + let mut builder = Builder::new_local(copy.to_str().unwrap()); + if *encrypted { + builder = builder + .experimental_encryption(true) + .with_encryption(EncryptionOpts { + cipher: ENCRYPTED_FIXTURE_CIPHER.to_string(), + hexkey: ENCRYPTED_FIXTURE_KEY.to_string(), + }); + } + let db = builder.build().await.unwrap(); + let conn = db.connect().unwrap(); + let detected = schema::detect_schema_version(&conn).await.unwrap(); + assert_eq!(detected, Some(*version), "{name}"); + + let mut rows = conn.query("PRAGMA user_version", ()).await.unwrap(); + let row = rows.next().await.unwrap().unwrap(); + let user_version: i64 = row.get(0).unwrap(); + assert_eq!(user_version, 0, "{name}: old fixtures predate user_version"); + } + + let committed = fixtures_dir().join("v0_4-encrypted.db"); + let copy = scratch.path().join("v0_4-encrypted-nokey.db"); + fs::copy(&committed, ©).unwrap(); + let without_key = async { + let db = Builder::new_local(copy.to_str().unwrap()).build().await?; + let conn = db.connect()?; + let mut rows = conn.query("SELECT COUNT(*) FROM fs_inode", ()).await?; + rows.next().await?; + Ok::<_, anyhow::Error>(()) + } + .await; + assert!( + without_key.is_err(), + "encrypted fixture must not open without the key" + ); +} + +async fn create_fixture(path: &Path, version: SchemaVersion, encrypted: bool) { + let mut builder = Builder::new_local(path.to_str().unwrap()); + if encrypted { + builder = builder + .experimental_encryption(true) + .with_encryption(EncryptionOpts { + cipher: ENCRYPTED_FIXTURE_CIPHER.to_string(), + hexkey: ENCRYPTED_FIXTURE_KEY.to_string(), + }); + } + let db = builder.build().await.unwrap(); + let conn = db.connect().unwrap(); + populate_fixture(&conn, version).await; + + let mut rows = conn + .query("PRAGMA wal_checkpoint(TRUNCATE)", ()) + .await + .unwrap(); + while rows.next().await.unwrap().is_some() {} + drop(conn); + drop(db); + for suffix in ["-wal", "-shm"] { + let sidecar = PathBuf::from(format!("{}{}", path.display(), suffix)); + if let Ok(metadata) = fs::metadata(&sidecar) { + assert_eq!( + metadata.len(), + 0, + "fixture sidecar {} not empty after checkpoint", + sidecar.display() + ); + fs::remove_file(&sidecar).unwrap(); + } + } +} + +async fn populate_fixture(conn: &Connection, version: SchemaVersion) { + conn.execute( + "CREATE TABLE fs_config (key TEXT PRIMARY KEY, value TEXT NOT NULL)", + (), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO fs_config (key, value) VALUES + ('schema_version', ?), + ('chunk_size', '4096'), + ('custom_metadata', 'fixture-preserve-me')", + (version.as_str(),), + ) + .await + .unwrap(); + + let mut inode_columns = vec![ + "ino INTEGER PRIMARY KEY AUTOINCREMENT", + "mode INTEGER NOT NULL", + "uid INTEGER NOT NULL DEFAULT 0", + "gid INTEGER NOT NULL DEFAULT 0", + "size INTEGER NOT NULL DEFAULT 0", + "atime INTEGER NOT NULL", + "mtime INTEGER NOT NULL", + "ctime INTEGER NOT NULL", + ]; + if version >= SchemaVersion::V0_2 { + inode_columns.insert(2, "nlink INTEGER NOT NULL DEFAULT 0"); + } + if version >= SchemaVersion::V0_4 { + inode_columns.extend([ + "rdev INTEGER NOT NULL DEFAULT 0", + "atime_nsec INTEGER NOT NULL DEFAULT 0", + "mtime_nsec INTEGER NOT NULL DEFAULT 0", + "ctime_nsec INTEGER NOT NULL DEFAULT 0", + ]); + } + conn.execute( + &format!("CREATE TABLE fs_inode ({})", inode_columns.join(", ")), + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_dentry ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parent_ino INTEGER NOT NULL, + ino INTEGER NOT NULL, + UNIQUE(parent_ino, name) + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE INDEX idx_fs_dentry_parent ON fs_dentry(parent_ino, name)", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_data ( + ino INTEGER NOT NULL, + chunk_index INTEGER NOT NULL, + data BLOB NOT NULL, + PRIMARY KEY (ino, chunk_index) + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE fs_symlink (ino INTEGER PRIMARY KEY, target TEXT NOT NULL)", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE kv_store ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + created_at INTEGER DEFAULT (unixepoch()), + updated_at INTEGER DEFAULT (unixepoch()) + )", + (), + ) + .await + .unwrap(); + conn.execute( + "CREATE TABLE tool_calls ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parameters TEXT, + result TEXT, + error TEXT, + status TEXT NOT NULL DEFAULT 'pending', + started_at INTEGER NOT NULL, + completed_at INTEGER, + duration_ms INTEGER + )", + (), + ) + .await + .unwrap(); + + let dir_mode: i64 = 0o040000 | 0o755; + let file_mode: i64 = 0o100000 | 0o644; + let symlink_mode: i64 = 0o120000 | 0o777; + let small = b"hello fixture".to_vec(); + let large: Vec = (0..(2 * 4096 + 37)) + .map(|index| 0x37_u8.wrapping_add((index % 251) as u8)) + .collect(); + + let inodes: [(i64, i64, i64, i64); 5] = [ + (1, dir_mode, 2, 0), + (2, dir_mode, 1, 0), + (3, file_mode, 1, small.len() as i64), + (4, file_mode, 2, large.len() as i64), + (5, symlink_mode, 1, 9), + ]; + for (ino, mode, nlink, size) in inodes { + let mut columns = vec![ + "ino", "mode", "uid", "gid", "size", "atime", "mtime", "ctime", + ]; + let mut values = vec![ + Value::Integer(ino), + Value::Integer(mode), + Value::Integer(1000), + Value::Integer(1000), + Value::Integer(size), + Value::Integer(1700000000), + Value::Integer(1700000000), + Value::Integer(1700000000), + ]; + if version >= SchemaVersion::V0_2 { + columns.insert(2, "nlink"); + values.insert(2, Value::Integer(nlink)); + } + if version >= SchemaVersion::V0_4 { + columns.extend(["rdev", "atime_nsec", "mtime_nsec", "ctime_nsec"]); + values.extend([ + Value::Integer(0), + Value::Integer(111), + Value::Integer(222), + Value::Integer(333), + ]); + } + let placeholders = std::iter::repeat_n("?", values.len()) + .collect::>() + .join(", "); + conn.execute( + &format!( + "INSERT INTO fs_inode ({}) VALUES ({placeholders})", + columns.join(", ") + ), + values, + ) + .await + .unwrap(); + } + + conn.execute( + "INSERT INTO fs_dentry (id, name, parent_ino, ino) VALUES + (1, 'dir', 1, 2), + (2, 'small.txt', 2, 3), + (3, 'large.bin', 2, 4), + (4, 'large-hardlink.bin', 2, 4), + (5, 'small-link', 2, 5)", + (), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO fs_symlink (ino, target) VALUES (5, 'small.txt')", + (), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO fs_data (ino, chunk_index, data) VALUES (3, 0, ?)", + (Value::Blob(small),), + ) + .await + .unwrap(); + for (chunk_index, chunk) in large.chunks(4096).enumerate() { + conn.execute( + "INSERT INTO fs_data (ino, chunk_index, data) VALUES (4, ?, ?)", + (chunk_index as i64, Value::Blob(chunk.to_vec())), + ) + .await + .unwrap(); + } + conn.execute( + "INSERT INTO kv_store (key, value, created_at, updated_at) + VALUES ('fixture', '{\"ok\":true}', 20, 21)", + (), + ) + .await + .unwrap(); + conn.execute( + "INSERT INTO tool_calls + (id, name, parameters, result, error, status, started_at, completed_at, duration_ms) + VALUES (1, 'fixture-tool', '{}', '{}', '', 'success', 30, 31, 42)", + (), + ) + .await + .unwrap(); +} diff --git a/cli/tests/syscall/Makefile b/crates/agentfs-cli/tests/syscall/Makefile similarity index 100% rename from cli/tests/syscall/Makefile rename to crates/agentfs-cli/tests/syscall/Makefile diff --git a/cli/tests/syscall/main.c b/crates/agentfs-cli/tests/syscall/main.c similarity index 100% rename from cli/tests/syscall/main.c rename to crates/agentfs-cli/tests/syscall/main.c diff --git a/cli/tests/syscall/test-append.c b/crates/agentfs-cli/tests/syscall/test-append.c similarity index 100% rename from cli/tests/syscall/test-append.c rename to crates/agentfs-cli/tests/syscall/test-append.c diff --git a/cli/tests/syscall/test-close.c b/crates/agentfs-cli/tests/syscall/test-close.c similarity index 100% rename from cli/tests/syscall/test-close.c rename to crates/agentfs-cli/tests/syscall/test-close.c diff --git a/cli/tests/syscall/test-common.h b/crates/agentfs-cli/tests/syscall/test-common.h similarity index 100% rename from cli/tests/syscall/test-common.h rename to crates/agentfs-cli/tests/syscall/test-common.h diff --git a/cli/tests/syscall/test-copyup-inode-stability.c b/crates/agentfs-cli/tests/syscall/test-copyup-inode-stability.c similarity index 100% rename from cli/tests/syscall/test-copyup-inode-stability.c rename to crates/agentfs-cli/tests/syscall/test-copyup-inode-stability.c diff --git a/cli/tests/syscall/test-copyup-permissions.c b/crates/agentfs-cli/tests/syscall/test-copyup-permissions.c similarity index 100% rename from cli/tests/syscall/test-copyup-permissions.c rename to crates/agentfs-cli/tests/syscall/test-copyup-permissions.c diff --git a/cli/tests/syscall/test-create-existing.c b/crates/agentfs-cli/tests/syscall/test-create-existing.c similarity index 100% rename from cli/tests/syscall/test-create-existing.c rename to crates/agentfs-cli/tests/syscall/test-create-existing.c diff --git a/cli/tests/syscall/test-dup.c b/crates/agentfs-cli/tests/syscall/test-dup.c similarity index 100% rename from cli/tests/syscall/test-dup.c rename to crates/agentfs-cli/tests/syscall/test-dup.c diff --git a/cli/tests/syscall/test-dup2.c b/crates/agentfs-cli/tests/syscall/test-dup2.c similarity index 100% rename from cli/tests/syscall/test-dup2.c rename to crates/agentfs-cli/tests/syscall/test-dup2.c diff --git a/cli/tests/syscall/test-fstat.c b/crates/agentfs-cli/tests/syscall/test-fstat.c similarity index 100% rename from cli/tests/syscall/test-fstat.c rename to crates/agentfs-cli/tests/syscall/test-fstat.c diff --git a/cli/tests/syscall/test-getdents64.c b/crates/agentfs-cli/tests/syscall/test-getdents64.c similarity index 100% rename from cli/tests/syscall/test-getdents64.c rename to crates/agentfs-cli/tests/syscall/test-getdents64.c diff --git a/cli/tests/syscall/test-link.c b/crates/agentfs-cli/tests/syscall/test-link.c similarity index 100% rename from cli/tests/syscall/test-link.c rename to crates/agentfs-cli/tests/syscall/test-link.c diff --git a/cli/tests/syscall/test-lstat.c b/crates/agentfs-cli/tests/syscall/test-lstat.c similarity index 100% rename from cli/tests/syscall/test-lstat.c rename to crates/agentfs-cli/tests/syscall/test-lstat.c diff --git a/cli/tests/syscall/test-mkfifo.c b/crates/agentfs-cli/tests/syscall/test-mkfifo.c similarity index 100% rename from cli/tests/syscall/test-mkfifo.c rename to crates/agentfs-cli/tests/syscall/test-mkfifo.c diff --git a/cli/tests/syscall/test-mknod.c b/crates/agentfs-cli/tests/syscall/test-mknod.c similarity index 100% rename from cli/tests/syscall/test-mknod.c rename to crates/agentfs-cli/tests/syscall/test-mknod.c diff --git a/cli/tests/syscall/test-open-readonly.c b/crates/agentfs-cli/tests/syscall/test-open-readonly.c similarity index 100% rename from cli/tests/syscall/test-open-readonly.c rename to crates/agentfs-cli/tests/syscall/test-open-readonly.c diff --git a/cli/tests/syscall/test-openat.c b/crates/agentfs-cli/tests/syscall/test-openat.c similarity index 100% rename from cli/tests/syscall/test-openat.c rename to crates/agentfs-cli/tests/syscall/test-openat.c diff --git a/cli/tests/syscall/test-pread-sparse.c b/crates/agentfs-cli/tests/syscall/test-pread-sparse.c similarity index 92% rename from cli/tests/syscall/test-pread-sparse.c rename to crates/agentfs-cli/tests/syscall/test-pread-sparse.c index 85f9445a..17327d77 100644 --- a/cli/tests/syscall/test-pread-sparse.c +++ b/crates/agentfs-cli/tests/syscall/test-pread-sparse.c @@ -30,13 +30,6 @@ int test_pread_sparse(const char *base_path) { char buf1[4096]; memset(buf1, 'A', sizeof(buf1)); ssize_t n = pwrite(fd, buf1, sizeof(buf1), 0); - if (n < 0 && errno == EBADF) { - /* Experimental sandbox doesn't support pwrite, skip test */ - printf(" pwrite not supported, skipping test\n"); - close(fd); - unlink(path); - return 0; - } TEST_ASSERT_ERRNO(n == sizeof(buf1), "pwrite at 0 should succeed"); /* Skip 8KB (create a hole), write at offset 12KB */ @@ -59,7 +52,6 @@ int test_pread_sparse(const char *base_path) { * 20480-24575: 'C' (written) */ - /* fsync is optional - experimental sandbox doesn't support it */ fsync(fd); close(fd); diff --git a/cli/tests/syscall/test-read.c b/crates/agentfs-cli/tests/syscall/test-read.c similarity index 100% rename from cli/tests/syscall/test-read.c rename to crates/agentfs-cli/tests/syscall/test-read.c diff --git a/cli/tests/syscall/test-rename.c b/crates/agentfs-cli/tests/syscall/test-rename.c similarity index 100% rename from cli/tests/syscall/test-rename.c rename to crates/agentfs-cli/tests/syscall/test-rename.c diff --git a/cli/tests/syscall/test-stat.c b/crates/agentfs-cli/tests/syscall/test-stat.c similarity index 100% rename from cli/tests/syscall/test-stat.c rename to crates/agentfs-cli/tests/syscall/test-stat.c diff --git a/cli/tests/syscall/test-unlink.c b/crates/agentfs-cli/tests/syscall/test-unlink.c similarity index 100% rename from cli/tests/syscall/test-unlink.c rename to crates/agentfs-cli/tests/syscall/test-unlink.c diff --git a/cli/tests/syscall/test-write.c b/crates/agentfs-cli/tests/syscall/test-write.c similarity index 100% rename from cli/tests/syscall/test-write.c rename to crates/agentfs-cli/tests/syscall/test-write.c diff --git a/crates/agentfs-cli/tests/test-cli-smoke.sh b/crates/agentfs-cli/tests/test-cli-smoke.sh new file mode 100755 index 00000000..d00068c5 --- /dev/null +++ b/crates/agentfs-cli/tests/test-cli-smoke.sh @@ -0,0 +1,335 @@ +#!/bin/sh +# +# CLI smoke suite (VAL-CLI-024): one fast end-to-end pass over the user-level +# command surface — init, run, exec, clone, fs, timeline, backup/materialize, +# integrity, migrate, MCP, ps, completions, and the deprecated `nfs` / +# `mcp-server` aliases. Deep per-command behavior lives in the dedicated +# suites; this gate proves every surface dispatches and succeeds. +set -eu + +echo -n "TEST cli smoke... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-cli-smoke.XXXXXX")" +NFS_PID="" +# Sessions run with the real HOME land in ~/.agentfs/run/; use unique +# ids so cleanup removes exactly the session dirs this test created (never +# sweep ~/.agentfs/run). +SESSION_PREFIX="smoke-$$" + +cleanup() { + if [ -n "$NFS_PID" ]; then + kill "$NFS_PID" 2>/dev/null || true + wait "$NFS_PID" 2>/dev/null || true + fi + rm -rf "$ROOT" \ + "${HOME:?}/.agentfs/run/${SESSION_PREFIX}-run" \ + "${HOME:?}/.agentfs/run/${SESSION_PREFIX}-exit-missing" \ + "${HOME:?}/.agentfs/run/${SESSION_PREFIX}-exit-nonexec" +} +trap cleanup EXIT INT TERM + +fail() { + echo "FAILED: $*" + exit 1 +} + +# Resolve the binary once: background legs must track the agentfs PID itself +# (backgrounding a wrapper orphans the real process on cleanup kill). +if [ -n "${AGENTFS_BIN:-}" ]; then + BIN="$AGENTFS_BIN" +else + cargo build --quiet --manifest-path "$CLI_DIR/Cargo.toml" || fail "could not build agentfs" + BIN="$CLI_DIR/../../target/debug/agentfs" +fi +[ -x "$BIN" ] || fail "agentfs binary not found at $BIN" + +run_agentfs() { + "$BIN" "$@" +} + +# The user's PATH may route `git` through a hook-manager shim that daemonizes +# out of test repos (library/environment.md); pin the distro binary and keep +# git config isolated. +mkdir -p "$ROOT/bin" +for candidate in /usr/bin/git /bin/git; do + if [ -x "$candidate" ]; then + ln -sf "$candidate" "$ROOT/bin/git" + break + fi +done +[ -e "$ROOT/bin/git" ] || ln -sf "$(command -v git)" "$ROOT/bin/git" +PATH="$ROOT/bin:$PATH" +export PATH +export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null + +cd "$ROOT" + +# --- init: create, refuse duplicate, --force ----------------------------------- +run_agentfs init smoke >"$ROOT/init.log" 2>&1 || fail "init failed: $(cat "$ROOT/init.log")" +DB="$ROOT/.agentfs/smoke.db" +[ -f "$DB" ] || fail "init did not create $DB" +# init's schema writes must be checkpointed away before it returns: a fresh +# init used to leave a ~119KB -wal next to the new DB (invariant I1). +[ ! -e "$DB-wal" ] || fail "init left $DB-wal behind" +[ ! -e "$DB-shm" ] || fail "init left $DB-shm behind" +if run_agentfs init smoke >"$ROOT/init-dup.log" 2>&1; then + fail "repeated init succeeded without --force" +fi +grep -q "already exists" "$ROOT/init-dup.log" || fail "duplicate init missing refusal message" +run_agentfs init smoke --force >/dev/null 2>&1 || fail "init --force failed" +[ ! -e "$DB-wal" ] || fail "init --force left $DB-wal behind" +[ ! -e "$DB-shm" ] || fail "init --force left $DB-shm behind" + +# --- init variants exit single-file too: --base (overlay) and -c (mount+command) -- +mkdir -p "$ROOT/initbase" +echo base-payload >"$ROOT/initbase/base.txt" +run_agentfs init smoke-base --base "$ROOT/initbase" >"$ROOT/init-base.log" 2>&1 || + fail "init --base failed: $(cat "$ROOT/init-base.log")" +BASE_DB="$ROOT/.agentfs/smoke-base.db" +[ -f "$BASE_DB" ] || fail "init --base did not create $BASE_DB" +[ ! -e "$BASE_DB-wal" ] || fail "init --base left $BASE_DB-wal behind" +[ ! -e "$BASE_DB-shm" ] || fail "init --base left $BASE_DB-shm behind" +run_agentfs init smoke-cmd -c 'echo init-cmd-ok' >"$ROOT/init-cmd.log" 2>&1 || + fail "init -c failed: $(cat "$ROOT/init-cmd.log")" +grep -q "init-cmd-ok" "$ROOT/init-cmd.log" || fail "init -c command output missing" +CMD_DB="$ROOT/.agentfs/smoke-cmd.db" +[ -f "$CMD_DB" ] || fail "init -c did not create $CMD_DB" +[ ! -e "$CMD_DB-wal" ] || fail "init -c left $CMD_DB-wal behind" +[ ! -e "$CMD_DB-shm" ] || fail "init -c left $CMD_DB-shm behind" + +# --- fs write/cat/ls ------------------------------------------------------------ +run_agentfs fs "$DB" write /smoke.txt smoke-payload >/dev/null 2>&1 || fail "fs write failed" +[ "$(run_agentfs fs "$DB" cat /smoke.txt 2>/dev/null)" = "smoke-payload" ] || fail "fs cat mismatch" +run_agentfs fs "$DB" ls / >"$ROOT/ls.log" 2>&1 || fail "fs ls failed" +grep -q "smoke.txt" "$ROOT/ls.log" || fail "fs ls does not list smoke.txt" + +# --- run (session sandbox) ------------------------------------------------------- +run_agentfs run --session "$SESSION_PREFIX-run" -- sh -c 'echo run-ok' >"$ROOT/run.log" 2>&1 || + fail "run failed: $(cat "$ROOT/run.log")" +grep -q "run-ok" "$ROOT/run.log" || fail "run output missing" + +# --- run session is audited in timeline (VAL-CROSS-006) --------------------------- +mkdir -p "$ROOT/home" +env HOME="$ROOT/home" "$BIN" run --session smoke-audit -- sh -c 'echo audit-ok' \ + >"$ROOT/run-audit.log" 2>&1 || + fail "audited run failed: $(cat "$ROOT/run-audit.log")" +AUDIT_DB="$ROOT/home/.agentfs/run/smoke-audit/delta.db" +[ -f "$AUDIT_DB" ] || fail "session delta.db missing at $AUDIT_DB" +# The audit reopen must finalize: a leftover WAL/SHM next to the session DB +# breaks the single-file invariant the run teardown just established. +[ ! -e "$AUDIT_DB-wal" ] || fail "run audit left $AUDIT_DB-wal behind" +[ ! -e "$AUDIT_DB-shm" ] || fail "run audit left $AUDIT_DB-shm behind" +run_agentfs timeline "$AUDIT_DB" --format json >"$ROOT/timeline-audit.json" 2>&1 || + fail "timeline on session DB failed: $(cat "$ROOT/timeline-audit.json")" +# Read-style reopens must restore the single-file family too. +[ ! -e "$AUDIT_DB-wal" ] || fail "timeline left $AUDIT_DB-wal behind" +[ ! -e "$AUDIT_DB-shm" ] || fail "timeline left $AUDIT_DB-shm behind" +grep -q '"name": "run"' "$ROOT/timeline-audit.json" || fail "timeline missing run audit row" +grep -q 'smoke-audit' "$ROOT/timeline-audit.json" || fail "run audit row missing session id" +grep -q '"status": "success"' "$ROOT/timeline-audit.json" || fail "run audit row not success" +grep -q 'exit_code' "$ROOT/timeline-audit.json" || fail "run audit row missing exit_code" + +# --- exec (mount-owning) ---------------------------------------------------------- +run_agentfs exec "$DB" sh -c 'echo exec-ok' >"$ROOT/exec.log" 2>&1 || + fail "exec failed: $(cat "$ROOT/exec.log")" +grep -q "exec-ok" "$ROOT/exec.log" || fail "exec output missing" + +# Regression: fs-write-created files must be chmod-able inside exec (they were +# owned by uid/gid 0, so chmod as the invoking uid failed EPERM). +run_agentfs exec "$DB" sh -c 'chmod 700 smoke.txt' >"$ROOT/exec-chmod.log" 2>&1 || + fail "chmod of an fs-write-created file failed inside exec: $(cat "$ROOT/exec-chmod.log")" + +# --- exec/run command-not-found conventions (VAL-CLI-019/020) ---------------------- +# Missing commands exit 127 and found-but-not-executable commands exit 126 via +# child-status passthrough; ordinary child exit codes still pass through. exec +# used to route these through the unified reporter and exit 1. +printf '#!/bin/sh\nexit 0\n' >"$ROOT/nonexec.sh" +chmod 644 "$ROOT/nonexec.sh" +rc=0 +run_agentfs exec "$DB" /definitely/missing >"$ROOT/exec-missing.log" 2>&1 || rc=$? +[ "$rc" -eq 127 ] || fail "exec of a missing absolute command exited $rc, want 127" +[ "$(grep -c '^Error:' "$ROOT/exec-missing.log")" -eq 1 ] || + fail "exec missing-command should print exactly one Error: line: $(cat "$ROOT/exec-missing.log")" +rc=0 +run_agentfs exec "$DB" definitely-missing-cmd-xyz >"$ROOT/exec-missing-path.log" 2>&1 || rc=$? +[ "$rc" -eq 127 ] || fail "exec of a missing PATH command exited $rc, want 127" +rc=0 +run_agentfs exec "$DB" "$ROOT/nonexec.sh" >"$ROOT/exec-nonexec.log" 2>&1 || rc=$? +[ "$rc" -eq 126 ] || fail "exec of a non-executable command exited $rc, want 126" +rc=0 +run_agentfs exec "$DB" sh -c 'exit 43' >"$ROOT/exec-43.log" 2>&1 || rc=$? +[ "$rc" -eq 43 ] || fail "exec child exit status not passed through: got $rc, want 43" +rc=0 +run_agentfs run --session "$SESSION_PREFIX-exit-missing" -- /definitely/missing \ + >"$ROOT/run-missing.log" 2>&1 || rc=$? +[ "$rc" -eq 127 ] || fail "run of a missing command exited $rc, want 127" +rc=0 +run_agentfs run --session "$SESSION_PREFIX-exit-nonexec" -- "$ROOT/nonexec.sh" \ + >"$ROOT/run-nonexec.log" 2>&1 || rc=$? +[ "$rc" -eq 126 ] || fail "run of a non-executable command exited $rc, want 126" + +# --- clone a local git repo into a fresh DB ---------------------------------------- +mkdir -p "$ROOT/srcrepo" +( + cd "$ROOT/srcrepo" + git init -q + git config user.email smoke@example.invalid + git config user.name Smoke + echo hello >hello.txt + git add hello.txt + git commit -q -m smoke +) || fail "could not build clone fixture repo" +run_agentfs clone "$ROOT/clone.db" "$ROOT/srcrepo" repo >"$ROOT/clone.log" 2>&1 || + fail "clone failed: $(cat "$ROOT/clone.log")" +run_agentfs fs "$ROOT/clone.db" ls / >"$ROOT/clone-ls.log" 2>&1 || + fail "fs ls on cloned DB failed" +grep -q "hello.txt" "$ROOT/clone-ls.log" || fail "cloned repo content missing from DB" + +# --- timeline ------------------------------------------------------------------------- +run_agentfs timeline "$DB" --format json >"$ROOT/timeline.log" 2>&1 || + fail "timeline failed: $(cat "$ROOT/timeline.log")" + +# --- backup --materialize and materialize -------------------------------------------- +run_agentfs backup "$DB" "$ROOT/backup.db" --verify --materialize >/dev/null 2>&1 || + fail "backup --verify --materialize failed" +[ -f "$ROOT/backup.db" ] || fail "backup did not create target" +run_agentfs materialize "$DB" --output "$ROOT/materialized.db" --verify >/dev/null 2>&1 || + fail "materialize --verify failed" +[ -f "$ROOT/materialized.db" ] || fail "materialize did not create target" + +# --- integrity ------------------------------------------------------------------------- +run_agentfs integrity --json "$DB" >"$ROOT/integrity.json" 2>&1 || + fail "integrity failed: $(cat "$ROOT/integrity.json")" + +# --- one-shot commands leave a single-file database family (invariant I1) --------------- +# Census every read-style reopen: each used to leave a header-only -wal next +# to the DB after exit, which the phase8 stress gate's size==0 unlink missed. +# fs write is censused too: it drained but left a truncated 0-byte -wal. +RDB="$ROOT/readonly.db" +cp "$ROOT/backup.db" "$RDB" +no_sidecars() { + [ ! -e "$RDB-wal" ] || fail "$1 left $RDB-wal behind" + [ ! -e "$RDB-shm" ] || fail "$1 left $RDB-shm behind" +} +no_sidecars "cp of backup.db" +run_agentfs timeline "$RDB" >/dev/null 2>&1 || fail "timeline (read census) failed" +no_sidecars "timeline" +run_agentfs timeline "$RDB" --format json >/dev/null 2>&1 || + fail "timeline --format json (read census) failed" +no_sidecars "timeline --format json" +run_agentfs fs "$RDB" ls / >/dev/null 2>&1 || fail "fs ls (read census) failed" +no_sidecars "fs ls" +run_agentfs fs "$RDB" cat /smoke.txt >/dev/null 2>&1 || fail "fs cat (read census) failed" +no_sidecars "fs cat" +if run_agentfs fs "$RDB" cat /missing.txt >/dev/null 2>&1; then + fail "fs cat of a missing file succeeded" +fi +no_sidecars "fs cat (error path)" +run_agentfs fs "$RDB" write /census-write.txt census-payload >/dev/null 2>&1 || + fail "fs write (census) failed" +no_sidecars "fs write" +[ "$(run_agentfs fs "$RDB" cat /census-write.txt 2>/dev/null)" = "census-payload" ] || + fail "fs write payload not durable after finalize" +run_agentfs diff "$RDB" >/dev/null 2>&1 || fail "diff (read census) failed" +no_sidecars "diff" +run_agentfs integrity --json "$RDB" >/dev/null 2>&1 || fail "integrity --json (read census) failed" +no_sidecars "integrity --json" +run_agentfs migrate "$RDB" >/dev/null 2>&1 || fail "migrate already-current (read census) failed" +no_sidecars "migrate (already current)" +cp "$DIR/fixtures/migrate/v0_4.db" "$ROOT/dry-run.db" +run_agentfs migrate "$ROOT/dry-run.db" --dry-run >/dev/null 2>&1 || fail "migrate --dry-run failed" +[ ! -e "$ROOT/dry-run.db-wal" ] || fail "migrate --dry-run left $ROOT/dry-run.db-wal behind" +[ ! -e "$ROOT/dry-run.db-shm" ] || fail "migrate --dry-run left $ROOT/dry-run.db-shm behind" +# sync stats errors on a non-synced local DB but must still exit single-file +run_agentfs sync "$RDB" stats >/dev/null 2>&1 || true +no_sidecars "sync stats" +run_agentfs ps >/dev/null 2>&1 || fail "ps (read census) failed" +no_sidecars "ps" +# exec is mount-owning: teardown chdirs to / before the sidecar sweep runs, and +# agent-id resolution hands core a cwd-relative db path, which used to leave a +# truncated 0-byte -wal next to the DB after exit. +EXEC_DB="$ROOT/.agentfs/exec-census.db" +cp "$RDB" "$EXEC_DB" +run_agentfs exec exec-census sh -c 'echo exec-census-ok' >"$ROOT/exec-census.log" 2>&1 || + fail "exec (census) failed: $(cat "$ROOT/exec-census.log")" +grep -q "exec-census-ok" "$ROOT/exec-census.log" || fail "exec (census) output missing" +[ ! -e "$EXEC_DB-wal" ] || fail "exec left $EXEC_DB-wal behind" +[ ! -e "$EXEC_DB-shm" ] || fail "exec left $EXEC_DB-shm behind" + +# --- migrate a committed old-schema fixture ---------------------------------------------- +cp "$DIR/fixtures/migrate/v0_4.db" "$ROOT/old.db" +run_agentfs migrate "$ROOT/old.db" >"$ROOT/migrate.log" 2>&1 || + fail "migrate failed: $(cat "$ROOT/migrate.log")" +run_agentfs integrity --json "$ROOT/old.db" >/dev/null 2>&1 || + fail "integrity on migrated fixture failed" + +# Regression: a nonexistent path-shaped argument must report a missing +# database, not "invalid agent ID". +if run_agentfs migrate "$ROOT/definitely/missing.db" >"$ROOT/migrate-missing.log" 2>&1; then + fail "migrate of a missing path succeeded" +fi +grep -qi "database not found" "$ROOT/migrate-missing.log" || + fail "migrate missing-path error is not a not-found report: $(cat "$ROOT/migrate-missing.log")" + +# --- ps ------------------------------------------------------------------------------------ +run_agentfs ps >/dev/null 2>&1 || fail "ps failed" + +# --- completions ----------------------------------------------------------------------------- +run_agentfs completions show >"$ROOT/completions.log" 2>&1 || fail "completions show failed" + +# --- MCP over stdio via the deprecated `mcp-server` alias ------------------------------------- +python3 - "$BIN" "$DB" <<'PY' || fail "mcp-server alias initialize round trip failed" +import json +import subprocess +import sys + +bin_path, db = sys.argv[1], sys.argv[2] +argv = [bin_path, "mcp-server", db] +proc = subprocess.Popen( + argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True +) +request = { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": {"name": "cli-smoke", "version": "0"}, + }, +} +proc.stdin.write(json.dumps(request) + "\n") +proc.stdin.flush() +line = proc.stdout.readline() +proc.stdin.close() +try: + proc.wait(timeout=30) +except subprocess.TimeoutExpired: + proc.kill() + raise SystemExit("mcp-server did not exit after stdin close") +reply = json.loads(line) +if reply.get("id") != 1 or "result" not in reply: + raise SystemExit(f"unexpected initialize reply: {reply}") +PY + +# --- deprecated `nfs` alias serves on an ephemeral port ------------------------------------------ +"$BIN" nfs "$DB" --port 0 >"$ROOT/nfs.log" 2>&1 & +NFS_PID=$! +WAITED=0 +while [ "$WAITED" -lt 40 ]; do + if grep -q "AgentFS NFS Server" "$ROOT/nfs.log" 2>/dev/null; then + break + fi + kill -0 "$NFS_PID" 2>/dev/null || fail "nfs alias exited early: $(cat "$ROOT/nfs.log")" + sleep 0.25 + WAITED=$((WAITED + 1)) +done +grep -q "AgentFS NFS Server" "$ROOT/nfs.log" || fail "nfs alias never reported startup" +kill "$NFS_PID" 2>/dev/null || true +wait "$NFS_PID" 2>/dev/null || true +NFS_PID="" + +echo "OK" diff --git a/cli/tests/test-corruption-torture.sh b/crates/agentfs-cli/tests/test-corruption-torture.sh similarity index 87% rename from cli/tests/test-corruption-torture.sh rename to crates/agentfs-cli/tests/test-corruption-torture.sh index 0b2aec63..b36cf945 100755 --- a/cli/tests/test-corruption-torture.sh +++ b/crates/agentfs-cli/tests/test-corruption-torture.sh @@ -22,6 +22,7 @@ RUSTUP_TOOLCHAIN_FOR_TEST="${RUSTUP_TOOLCHAIN:-nightly}" WORKERS="${CORRUPTION_TORTURE_WORKERS:-4}" ITERATIONS="${CORRUPTION_TORTURE_ITERATIONS:-3}" TEST_TIMEOUT="${CORRUPTION_TORTURE_TIMEOUT:-90}" +TEARDOWN_TIMEOUT="${CORRUPTION_TORTURE_TEARDOWN_TIMEOUT:-10}" INTEGRITY_INTERVAL="${CORRUPTION_TORTURE_INTEGRITY_INTERVAL:-1}" INTEGRITY_TIMEOUT="${CORRUPTION_TORTURE_INTEGRITY_TIMEOUT:-5}" START_TIMEOUT="${CORRUPTION_TORTURE_START_TIMEOUT:-30}" @@ -45,6 +46,7 @@ MONITOR_OVERLAP_COUNT="" OWNER_LOG="" MONITOR_LOG="" TIMEOUT_FLAG="" +OWNER_TEARDOWN_MS="" skip() { echo "SKIP: $*" @@ -80,6 +82,7 @@ validate_positive_integer() { validate_positive_integer CORRUPTION_TORTURE_WORKERS "$WORKERS" validate_positive_integer CORRUPTION_TORTURE_ITERATIONS "$ITERATIONS" validate_positive_integer CORRUPTION_TORTURE_TIMEOUT "$TEST_TIMEOUT" +validate_positive_integer CORRUPTION_TORTURE_TEARDOWN_TIMEOUT "$TEARDOWN_TIMEOUT" validate_positive_integer CORRUPTION_TORTURE_INTEGRITY_TIMEOUT "$INTEGRITY_TIMEOUT" validate_positive_integer CORRUPTION_TORTURE_START_TIMEOUT "$START_TIMEOUT" @@ -181,6 +184,25 @@ WORKDIR="$TEST_ROOT/work" LOGDIR="$TEST_ROOT/logs" mkdir -p "$TEST_HOME/.cache" "$TEST_HOME/.config" "$WORKDIR" "$LOGDIR" +# The user's PATH may route `git` through a hook-manager shim that daemonizes +# out of test repos (library/environment.md); pin the distro binary and give +# the temp HOME a hookless git config so nothing survives the suite. +mkdir -p "$TEST_HOME/bin" "$TEST_HOME/git-hooks-none" +GIT_REAL="" +for candidate in /usr/bin/git /bin/git; do + if [ -x "$candidate" ]; then + GIT_REAL="$candidate" + break + fi +done +[ -n "$GIT_REAL" ] || GIT_REAL="$(command -v git)" +ln -sf "$GIT_REAL" "$TEST_HOME/bin/git" +printf '[core]\n\thooksPath = %s\n' "$TEST_HOME/git-hooks-none" >"$TEST_HOME/.gitconfig" +PATH="$TEST_HOME/bin:$PATH" +# The PATH shim only holds host-side: inside `agentfs run` temp dirs are +# hidden, so sandboxed workloads must call "$GIT_REAL" by absolute path. +export PATH GIT_REAL + SESSION_ID="corruption-torture-$$" RUN_DIR="$TEST_HOME/.agentfs/run/$SESSION_ID" DELTA_DB="$RUN_DIR/delta.db" @@ -408,18 +430,18 @@ while [ "$i" -le "$iterations" ]; do done cd "$base/repo" -git init -q -git config user.email "worker-$worker@example.invalid" -git config user.name "Worker $worker" +"${GIT_REAL:-git}" init -q +"${GIT_REAL:-git}" config user.email "worker-$worker@example.invalid" +"${GIT_REAL:-git}" config user.name "Worker $worker" i=1 while [ "$i" -le "$iterations" ]; do mkdir -p "src/iter-$i" printf "repo worker=%s iteration=%s\n" "$worker" "$i" > "src/iter-$i/file.txt" printf "commit worker=%s iteration=%s\n" "$worker" "$i" >> journal.txt - git add . - git commit -q -m "worker $worker iteration $i" - git fsck --strict --no-progress + "${GIT_REAL:-git}" add . + "${GIT_REAL:-git}" commit -q -m "worker $worker iteration $i" + "${GIT_REAL:-git}" fsck --strict --no-progress i=$((i + 1)) done ' worker "$worker" "$ITERATIONS" @@ -514,8 +536,8 @@ while [ "$w" -le "$workers" ]; do i=$((i + 1)) done - git -C "$base/repo" fsck --strict --no-progress - commits="$(git -C "$base/repo" rev-list --count HEAD)" + "${GIT_REAL:-git}" -C "$base/repo" fsck --strict --no-progress + commits="$("${GIT_REAL:-git}" -C "$base/repo" rev-list --count HEAD)" test "$commits" -eq "$iterations" w=$((w + 1)) @@ -531,8 +553,19 @@ done terminate_owner() { if [ -n "$OWNER_PID" ]; then kill "$OWNER_PID" 2>/dev/null || true + start_ms="$(date +%s%3N)" + deadline_ms=$((start_ms + TEARDOWN_TIMEOUT * 1000)) + while kill -0 "$OWNER_PID" 2>/dev/null; do + now_ms="$(date +%s%3N)" + if [ "$now_ms" -ge "$deadline_ms" ]; then + fail "owner did not exit within ${TEARDOWN_TIMEOUT}s after SIGTERM" + fi + sleep 0.1 + done wait "$OWNER_PID" 2>/dev/null || true OWNER_PID="" + end_ms="$(date +%s%3N)" + OWNER_TEARDOWN_MS=$((end_ms - start_ms)) fi } @@ -569,4 +602,14 @@ integrity_check "$DELTA_DB" "$INTEGRITY_TIMEOUT" >>"$MONITOR_LOG" 2>&1 || run_final_session_checks terminate_owner -echo "OK (workers=$WORKERS iterations=$ITERATIONS)" +if [ "${AGENTFS_FUSE_URING:-}" = "1" ] && [ -r /sys/module/fuse/parameters/enable_uring ] && + [ "$(cat /sys/module/fuse/parameters/enable_uring)" = "Y" ] && + ! grep -q "advertising FUSE_OVER_IO_URING" "$OWNER_LOG"; then + fail "uring leg did not advertise FUSE_OVER_IO_URING" +fi + +if [ "${AGENTFS_FUSE_URING:-}" = "0" ] && grep -q "advertising FUSE_OVER_IO_URING" "$OWNER_LOG"; then + fail "legacy leg unexpectedly used FUSE_OVER_IO_URING" +fi + +echo "OK (workers=$WORKERS iterations=$ITERATIONS owner_teardown_ms=$OWNER_TEARDOWN_MS)" diff --git a/crates/agentfs-cli/tests/test-ephemeral-sidecar-cleanup.sh b/crates/agentfs-cli/tests/test-ephemeral-sidecar-cleanup.sh new file mode 100755 index 00000000..e8c1eeb6 --- /dev/null +++ b/crates/agentfs-cli/tests/test-ephemeral-sidecar-cleanup.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env sh +set -eu + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +echo -n "TEST ephemeral sidecar cleanup... " + +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-sidecar-cleanup.XXXXXX")" +PINNED_TMP="$ROOT/tmp" +WORK="$ROOT/work" +MNT="$ROOT/mnt" +mkdir -p "$PINNED_TMP" "$WORK" "$MNT" "$ROOT/home" + +MOUNT_PID="" + +cleanup() { + if mountpoint -q "$MNT" 2>/dev/null; then + fusermount3 -u "$MNT" >/dev/null 2>&1 || true + fi + if [ -n "$MOUNT_PID" ] && kill -0 "$MOUNT_PID" 2>/dev/null; then + kill "$MOUNT_PID" 2>/dev/null || true + wait "$MOUNT_PID" 2>/dev/null || true + fi + rm -rf "$ROOT" +} +trap cleanup EXIT INT TERM + +# Resolve the binary once: the mount leg must track the agentfs PID itself +# and reap it before the test exits (a lingering FUSE connection draining in +# the kernel wedges the next fuse-over-io_uring mount attempt forever). +if [ -n "${AGENTFS_BIN:-}" ]; then + BIN="$AGENTFS_BIN" +else + cargo +nightly build --quiet --manifest-path "$CLI_DIR/Cargo.toml" || { + echo "FAILED: could not build agentfs" + exit 1 + } + BIN="$CLI_DIR/../../target/debug/agentfs" +fi +if [ ! -x "$BIN" ]; then + echo "FAILED: agentfs binary not found at $BIN" + exit 1 +fi + +run_agentfs() { + "$BIN" "$@" +} + +sidecar_count() { + find "$PINNED_TMP" -maxdepth 1 -type f -name 'tursodb-ephemeral-*' -print | wc -l | tr -d ' ' +} + +spill_dir_count() { + find "$PINNED_TMP" -maxdepth 1 -type d -name 'agentfs-spill-*' -print | wc -l | tr -d ' ' +} + +assert_no_sidecars() { + phase="$1" + count="$(sidecar_count)" + spills="$(spill_dir_count)" + printf '\n%s sidecar_count=%s spill_dir_count=%s\n' "$phase" "$count" "$spills" + if [ "$count" -ne 0 ]; then + echo "FAILED: residual tursodb ephemeral sidecars after $phase" + find "$PINNED_TMP" -maxdepth 1 -type f -name 'tursodb-ephemeral-*' -printf '%f %s\n' | sort + exit 1 + fi + # The CLI's private TMPDIR spill dir must be cleaned up when the process + # exits (no process of ours is running between phases). + if [ "$spills" -ne 0 ]; then + echo "FAILED: residual agentfs-spill dirs after $phase" + find "$PINNED_TMP" -maxdepth 1 -type d -name 'agentfs-spill-*' -print | sort + exit 1 + fi +} + +export TMPDIR="$PINNED_TMP" +export TMP="$PINNED_TMP" +export TEMP="$PINNED_TMP" +export HOME="$ROOT/home" + +# The user's PATH may route `git` through a hook-manager shim that daemonizes +# out of test repos (library/environment.md); pin the distro binary and give +# the temp HOME a hookless git config. +mkdir -p "$HOME/bin" "$HOME/git-hooks-none" +GIT_REAL="" +for candidate in /usr/bin/git /bin/git; do + if [ -x "$candidate" ]; then + GIT_REAL="$candidate" + break + fi +done +[ -n "$GIT_REAL" ] || GIT_REAL="$(command -v git)" +ln -sf "$GIT_REAL" "$HOME/bin/git" +printf '[core]\n\thooksPath = %s\n' "$HOME/git-hooks-none" >"$HOME/.gitconfig" +PATH="$HOME/bin:$PATH" +export PATH + +cd "$WORK" + +assert_no_sidecars "before" +run_agentfs init sidecar +assert_no_sidecars "init" +run_agentfs fs sidecar write /hello.txt "hello sidecar" +assert_no_sidecars "fs-write" +run_agentfs fs sidecar cat /hello.txt >/dev/null +assert_no_sidecars "fs-cat" +run_agentfs run --session sidecar-run -- sh -c 'printf run-data > run.txt && cat run.txt' >/dev/null +assert_no_sidecars "run" + +# The private spill-dir TMPDIR override is process-internal: children of +# run/exec must see the user's TMPDIR, not the agentfs-spill-* dir. +# Mount tracing emits on stdout; the child's printf (no trailing newline) is +# always the last line of the captured stream. +CHILD_TMPDIR="$(run_agentfs run --session sidecar-tmpdir -- sh -c 'printf %s "${TMPDIR:-}"' 2>/dev/null | tail -n 1)" +if [ "$CHILD_TMPDIR" != "$PINNED_TMP" ]; then + echo "FAILED: run child saw TMPDIR='$CHILD_TMPDIR', expected '$PINNED_TMP'" + exit 1 +fi +assert_no_sidecars "run-child-tmpdir" +CHILD_TMPDIR="$(run_agentfs exec sidecar sh -c 'printf %s "${TMPDIR:-}"' 2>/dev/null | tail -n 1)" +if [ "$CHILD_TMPDIR" != "$PINNED_TMP" ]; then + echo "FAILED: exec child saw TMPDIR='$CHILD_TMPDIR', expected '$PINNED_TMP'" + exit 1 +fi +assert_no_sidecars "exec-child-tmpdir" +# Stale spill dirs from SIGKILLed processes are reaped on the next CLI start. +mkdir -p "$PINNED_TMP/agentfs-spill-99999999-0" +run_agentfs fs sidecar cat /hello.txt >/dev/null +if [ -d "$PINNED_TMP/agentfs-spill-99999999-0" ]; then + echo "FAILED: stale dead-owner spill dir was not garbage-collected" + exit 1 +fi +assert_no_sidecars "stale-spill-gc" +GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null run_agentfs run --session sidecar-git -- sh -c ' + set -eu + git init repo >/dev/null + cd repo + git config user.email agentfs@example.invalid + git config user.name AgentFS + mkdir -p src + for i in 1 2 3 4; do + printf "token %s\n" "$i" > "src/file$i.txt" + done + git add src + git commit -m initial >/dev/null + git status --short >/dev/null + git grep token >/dev/null + printf change >> src/file1.txt + git diff -- src/file1.txt >/dev/null + git checkout -- src/file1.txt + git fsck --no-dangling >/dev/null +' >/dev/null +assert_no_sidecars "run-git" +"$BIN" mount sidecar "$MNT" --foreground >"$ROOT/mount.log" 2>&1 & +MOUNT_PID=$! + +mounted=0 +i=0 +while [ "$i" -lt 100 ]; do + if mountpoint -q "$MNT"; then + mounted=1 + break + fi + if ! kill -0 "$MOUNT_PID" 2>/dev/null; then + break + fi + i=$((i + 1)) + sleep 0.1 +done + +if [ "$mounted" -ne 1 ]; then + echo "FAILED: mount did not become live" + sed 's/^/ /' "$ROOT/mount.log" || true + exit 1 +fi + +printf mounted-data > "$MNT/mounted.txt" +cat "$MNT/mounted.txt" >/dev/null +fusermount3 -u "$MNT" + +i=0 +while kill -0 "$MOUNT_PID" 2>/dev/null && [ "$i" -lt 100 ]; do + i=$((i + 1)) + sleep 0.1 +done +if kill -0 "$MOUNT_PID" 2>/dev/null; then + echo "FAILED: mount process did not exit after unmount" + exit 1 +fi +wait "$MOUNT_PID" 2>/dev/null || true +MOUNT_PID="" + +assert_no_sidecars "mount-unmount" + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-fuse-cache-invalidation.sh b/crates/agentfs-cli/tests/test-fuse-cache-invalidation.sh new file mode 100755 index 00000000..5baef55c --- /dev/null +++ b/crates/agentfs-cli/tests/test-fuse-cache-invalidation.sh @@ -0,0 +1,216 @@ +#!/bin/sh +# +# Test that FUSE kernel cache is properly invalidated after mutations. +# +# After readdirplus populates the dcache, unlink/rmdir/rename must +# invalidate the affected entries so subsequent readdir sees the change. +# +set -eu + +echo -n "TEST fuse cache invalidation after mutations... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +TEST_AGENT_ID="test-fuse-cache-inval-agent" +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-cache-inval.XXXXXX")" +MOUNTPOINT="$ROOT/mnt" +MOUNT_PID="" + +unmount_quiet() { + fusermount3 -u "$MOUNTPOINT" 2>/dev/null || + fusermount -u "$MOUNTPOINT" 2>/dev/null || true +} + +cleanup() { + unmount_quiet + if [ -n "$MOUNT_PID" ]; then + kill "$MOUNT_PID" 2>/dev/null || true + wait "$MOUNT_PID" 2>/dev/null || true + fi + rm -rf "$ROOT" +} +trap cleanup EXIT INT TERM + +fail() { + echo "FAILED: $*" + exit 1 +} + +# Resolve the binary once: background mount legs must track the agentfs PID +# itself (backgrounding a wrapper orphans the real process on cleanup kill). +if [ -n "${AGENTFS_BIN:-}" ]; then + BIN="$AGENTFS_BIN" +else + cargo build --quiet --manifest-path "$CLI_DIR/Cargo.toml" || { + echo "FAILED: could not build agentfs" + exit 1 + } + BIN="$CLI_DIR/../../target/debug/agentfs" +fi +if [ ! -x "$BIN" ]; then + echo "FAILED: agentfs binary not found at $BIN" + exit 1 +fi + +run_agentfs() { + "$BIN" "$@" +} + +# The session DB lands under $ROOT/.agentfs instead of the repo working tree. +cd "$ROOT" + +# Initialize the database +run_agentfs init "$TEST_AGENT_ID" > /dev/null 2>&1 + +# Create mountpoint +mkdir -p "$MOUNTPOINT" + +# Mount in foreground mode (background it ourselves so we can control it) +"$BIN" mount ".agentfs/${TEST_AGENT_ID}.db" "$MOUNTPOINT" --foreground & +MOUNT_PID=$! + +# Wait for mount to be ready +MAX_WAIT=20 +WAITED=0 +while [ $WAITED -lt $MAX_WAIT ]; do + if mountpoint -q "$MOUNTPOINT" 2>/dev/null; then + break + fi + sleep 0.5 + WAITED=$((WAITED + 1)) +done + +mountpoint -q "$MOUNTPOINT" 2>/dev/null || fail "mount did not become ready in time" + +# Test 1: unlink should not leave stale entries in readdir +# Populate the directory with files +echo "content1" > "$MOUNTPOINT/file1.txt" +echo "content2" > "$MOUNTPOINT/file2.txt" +echo "content3" > "$MOUNTPOINT/file3.txt" + +# Prime the kernel dcache via stat + ls (readdirplus populates entries) +ls -la "$MOUNTPOINT" > /dev/null +stat "$MOUNTPOINT/file1.txt" > /dev/null 2>&1 +stat "$MOUNTPOINT/file2.txt" > /dev/null 2>&1 +stat "$MOUNTPOINT/file3.txt" > /dev/null 2>&1 +ls -la "$MOUNTPOINT" > /dev/null + +# Delete file1 and verify readdir no longer shows it +rm "$MOUNTPOINT/file1.txt" + +LS_OUTPUT=$(ls "$MOUNTPOINT") +if echo "$LS_OUTPUT" | grep -q "file1.txt"; then + fail "readdir still shows file1.txt after unlink +ls output was: $LS_OUTPUT" +fi + +echo "$LS_OUTPUT" | grep -q "file2.txt" || fail "file2.txt disappeared" + +if stat "$MOUNTPOINT/file1.txt" > /dev/null 2>&1; then + fail "stat still resolves file1.txt after unlink" +fi + +# Test 2: rmdir should not leave stale entries in readdir +mkdir "$MOUNTPOINT/subdir" +ls -la "$MOUNTPOINT" > /dev/null +stat "$MOUNTPOINT/subdir" > /dev/null 2>&1 +ls -la "$MOUNTPOINT" > /dev/null + +rmdir "$MOUNTPOINT/subdir" + +LS_OUTPUT=$(ls "$MOUNTPOINT") +if echo "$LS_OUTPUT" | grep -q "subdir"; then + fail "readdir still shows subdir after rmdir +ls output was: $LS_OUTPUT" +fi +if stat "$MOUNTPOINT/subdir" > /dev/null 2>&1; then + fail "stat still resolves subdir after rmdir" +fi + +# Test 3: rename should not leave stale source entry in readdir +echo "rename me" > "$MOUNTPOINT/before.txt" +ls -la "$MOUNTPOINT" > /dev/null +stat "$MOUNTPOINT/before.txt" > /dev/null 2>&1 +ls -la "$MOUNTPOINT" > /dev/null + +mv "$MOUNTPOINT/before.txt" "$MOUNTPOINT/after.txt" + +LS_OUTPUT=$(ls "$MOUNTPOINT") +if echo "$LS_OUTPUT" | grep -q "before.txt"; then + fail "readdir still shows before.txt after rename +ls output was: $LS_OUTPUT" +fi + +echo "$LS_OUTPUT" | grep -q "after.txt" || fail "after.txt not visible after rename +ls output was: $LS_OUTPUT" + +if stat "$MOUNTPOINT/before.txt" > /dev/null 2>&1; then + fail "stat still resolves before.txt after rename" +fi +[ "$(cat "$MOUNTPOINT/after.txt")" = "rename me" ] || + fail "after.txt content is stale after rename" + +# Test 4: create must defeat a cached negative dentry +# Prime a negative dentry: stat a name that doesn't exist yet +ls -la "$MOUNTPOINT" > /dev/null +stat "$MOUNTPOINT/negfile.txt" > /dev/null 2>&1 || true # caches ENOENT +ls -la "$MOUNTPOINT" > /dev/null # readdirplus confirms absence + +echo "new file" > "$MOUNTPOINT/negfile.txt" + +# stat must resolve (not serve cached ENOENT) +stat "$MOUNTPOINT/negfile.txt" > /dev/null 2>&1 || + fail "stat returns ENOENT for negfile.txt after create (negative dentry not invalidated)" + +# readdir must list it +LS_OUTPUT=$(ls "$MOUNTPOINT") +echo "$LS_OUTPUT" | grep -q "negfile.txt" || fail "readdir does not show negfile.txt after create +ls output was: $LS_OUTPUT" + +# Test 5: mkdir must defeat a cached negative dentry +ls -la "$MOUNTPOINT" > /dev/null +stat "$MOUNTPOINT/negdir" > /dev/null 2>&1 || true # caches ENOENT +ls -la "$MOUNTPOINT" > /dev/null # readdirplus confirms absence + +mkdir "$MOUNTPOINT/negdir" + +stat "$MOUNTPOINT/negdir" > /dev/null 2>&1 || + fail "stat returns ENOENT for negdir after mkdir (negative dentry not invalidated)" + +LS_OUTPUT=$(ls "$MOUNTPOINT") +echo "$LS_OUTPUT" | grep -q "negdir" || fail "readdir does not show negdir after mkdir +ls output was: $LS_OUTPUT" + +# Test 6: truncate must invalidate stale attrs and cached file data +printf "abcdefghij" > "$MOUNTPOINT/truncate.txt" +cat "$MOUNTPOINT/truncate.txt" > /dev/null +stat "$MOUNTPOINT/truncate.txt" > /dev/null 2>&1 +truncate -s 4 "$MOUNTPOINT/truncate.txt" + +TRUNC_SIZE=$(wc -c < "$MOUNTPOINT/truncate.txt" | tr -d ' ') +[ "$TRUNC_SIZE" = "4" ] || fail "truncate.txt size is stale after truncate: $TRUNC_SIZE" +TRUNC_CONTENT=$(cat "$MOUNTPOINT/truncate.txt") +[ "$TRUNC_CONTENT" = "abcd" ] || fail "truncate.txt content is stale after truncate: $TRUNC_CONTENT" + +# Test 7: repeated read/open cache must not serve stale data after write +printf "cache-before" > "$MOUNTPOINT/keep-cache.txt" +cat "$MOUNTPOINT/keep-cache.txt" > /dev/null +cat "$MOUNTPOINT/keep-cache.txt" > /dev/null +printf "cache-after" > "$MOUNTPOINT/keep-cache.txt" +KEEP_CACHE_CONTENT=$(cat "$MOUNTPOINT/keep-cache.txt") +[ "$KEEP_CACHE_CONTENT" = "cache-after" ] || + fail "keep-cache.txt content is stale after overwrite: $KEEP_CACHE_CONTENT" +truncate -s 5 "$MOUNTPOINT/keep-cache.txt" +KEEP_CACHE_CONTENT=$(cat "$MOUNTPOINT/keep-cache.txt") +[ "$KEEP_CACHE_CONTENT" = "cache" ] || + fail "keep-cache.txt content is stale after truncate: $KEEP_CACHE_CONTENT" + +# Unmount +unmount_quiet + +# Wait for mount process to exit +wait $MOUNT_PID 2>/dev/null || true +MOUNT_PID="" + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-fuse-sigint-panic-census.sh b/crates/agentfs-cli/tests/test-fuse-sigint-panic-census.sh new file mode 100755 index 00000000..700a4416 --- /dev/null +++ b/crates/agentfs-cli/tests/test-fuse-sigint-panic-census.sh @@ -0,0 +1,360 @@ +#!/bin/sh +# +# Panic census for repeated SIGINT teardown cycles. +# +# This regression targets a masked FUSE-over-io_uring queue-thread panic where +# the shutdown wake CQE used u64::MAX as user_data and the queue loop treated it +# as a ring slot. Cleanup still completed, so the only visible failure was +# stderr containing "panicked at". +# +set -eu + +echo -n "TEST FUSE SIGINT panic census... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" +CARGO_MANIFEST="$CLI_DIR/Cargo.toml" +AGENTFS_BIN="${AGENTFS_BIN:-}" +CYCLES="${PANIC_CENSUS_CYCLES:-8}" +TEARDOWN_TIMEOUT="${PANIC_CENSUS_TEARDOWN_TIMEOUT:-10}" +WORKLOAD_SECONDS="${PANIC_CENSUS_WORKLOAD_SECONDS:-1.5}" +HOST_HOME="${HOME:-}" +CARGO_HOME_FOR_TEST="${CARGO_HOME:-$HOST_HOME/.cargo}" +RUSTUP_HOME_FOR_TEST="${RUSTUP_HOME:-$HOST_HOME/.rustup}" +RUSTUP_TOOLCHAIN_FOR_TEST="${RUSTUP_TOOLCHAIN:-nightly}" + +TEST_ROOT="" +MOUNT_PID="" +WRITER_PID="" +CURRENT_MNT="" +PANIC_LINES="$( + mktemp "${TMPDIR:-/tmp}/agentfs-panic-census-lines.XXXXXX" +)" + +skip() { + echo "SKIP: $*" + exit 0 +} + +fail() { + echo + echo "FAILED: $*" + exit 1 +} + +validate_positive_integer() { + name="$1" + value="$2" + case "$value" in + ''|*[!0-9]*) + fail "$name must be a positive integer, got '$value'" + ;; + esac + if [ "$value" -le 0 ]; then + fail "$name must be a positive integer, got '$value'" + fi +} + +case "$(uname -s)" in + Linux) + ;; + *) + skip "requires Linux FUSE" + ;; +esac + +command -v cargo >/dev/null 2>&1 || skip "cargo is unavailable" +command -v mountpoint >/dev/null 2>&1 || skip "mountpoint is unavailable" +[ -e /dev/fuse ] || skip "requires /dev/fuse for FUSE mounts" + +if [ -r /proc/sys/kernel/unprivileged_userns_clone ] && + [ "$(cat /proc/sys/kernel/unprivileged_userns_clone)" = "0" ]; then + skip "unprivileged user namespaces are disabled" +fi + +validate_positive_integer PANIC_CENSUS_CYCLES "$CYCLES" +validate_positive_integer PANIC_CENSUS_TEARDOWN_TIMEOUT "$TEARDOWN_TIMEOUT" + +if [ -z "$AGENTFS_BIN" ]; then + cargo +nightly build --quiet --manifest-path "$CARGO_MANIFEST" >/dev/null 2>&1 || + fail "failed to build agentfs CLI" + TARGET_DIR="$( + cargo +nightly metadata --format-version 1 --no-deps --manifest-path "$CARGO_MANIFEST" | + python3 -c 'import json, sys; print(json.load(sys.stdin)["target_directory"])' + )" || fail "failed to resolve cargo target directory" + AGENTFS_BIN="$TARGET_DIR/debug/agentfs" +fi + +if [ ! -x "$AGENTFS_BIN" ]; then + fail "AGENTFS_BIN is not executable: $AGENTFS_BIN" +fi + +if [ "$CYCLES" -lt 5 ]; then + fail "PANIC_CENSUS_CYCLES must be at least 5" +fi + +unmount_current() { + if [ -n "$CURRENT_MNT" ] && mountpoint -q "$CURRENT_MNT" 2>/dev/null; then + if command -v fusermount3 >/dev/null 2>&1; then + fusermount3 -u "$CURRENT_MNT" 2>/dev/null || + fusermount3 -uz "$CURRENT_MNT" 2>/dev/null || true + elif command -v fusermount >/dev/null 2>&1; then + fusermount -u "$CURRENT_MNT" 2>/dev/null || true + elif command -v umount >/dev/null 2>&1; then + umount "$CURRENT_MNT" 2>/dev/null || true + fi + fi +} + +cleanup_current() { + set +e + if [ -n "$WRITER_PID" ] && kill -0 "$WRITER_PID" 2>/dev/null; then + kill "$WRITER_PID" 2>/dev/null || true + wait "$WRITER_PID" 2>/dev/null || true + fi + WRITER_PID="" + + if [ -n "$MOUNT_PID" ] && kill -0 "$MOUNT_PID" 2>/dev/null; then + kill -INT "$MOUNT_PID" 2>/dev/null || true + waited=0 + while kill -0 "$MOUNT_PID" 2>/dev/null && [ "$waited" -lt 20 ]; do + sleep 0.1 + waited=$((waited + 1)) + done + if kill -0 "$MOUNT_PID" 2>/dev/null; then + kill -KILL "$MOUNT_PID" 2>/dev/null || true + fi + wait "$MOUNT_PID" 2>/dev/null || true + fi + MOUNT_PID="" + + unmount_current + CURRENT_MNT="" + set -e +} + +cleanup_all() { + set +e + cleanup_current + if [ -n "$TEST_ROOT" ] && [ -d "$TEST_ROOT" ]; then + case "$TEST_ROOT" in + "${TMPDIR:-/tmp}"/agentfs-panic-census.*) + rm -rf "$TEST_ROOT" + ;; + *) + echo "WARNING: refusing to remove unexpected temp root: $TEST_ROOT" + ;; + esac + fi + rm -f "$PANIC_LINES" + set -e +} + +trap cleanup_all EXIT + +start_writer() { + mountpoint_path="$1" + ( + i=0 + while :; do + if ! { + printf 'panic census payload %s\n' "$i" >"$mountpoint_path/file-$((i % 8)).txt" + } 2>/dev/null; then + exit 0 + fi + cat "$mountpoint_path/file-$((i % 8)).txt" >/dev/null 2>&1 || exit 0 + i=$((i + 1)) + sleep 0.02 + done + ) & + WRITER_PID=$! +} + +wait_for_mount() { + mountpoint_path="$1" + deadline=$(( $(date +%s) + 20 )) + while [ "$(date +%s)" -le "$deadline" ]; do + if mountpoint -q "$mountpoint_path" 2>/dev/null; then + return 0 + fi + if ! kill -0 "$MOUNT_PID" 2>/dev/null; then + return 1 + fi + sleep 0.1 + done + return 1 +} + +wait_for_mount_exit() { + deadline=$(( $(date +%s) + TEARDOWN_TIMEOUT )) + while kill -0 "$MOUNT_PID" 2>/dev/null; do + if [ "$(date +%s)" -ge "$deadline" ]; then + return 1 + fi + sleep 0.05 + done + wait "$MOUNT_PID" 2>/dev/null || true + MOUNT_PID="" + return 0 +} + +scan_panic_lines() { + census_label="$1" + census_log="$2" + if awk -v label="$census_label" ' + /panicked at/ { + printf "%s:%d:%s\n", label, NR, $0 + found = 1 + } + END { exit found ? 0 : 1 } + ' "$census_log" >>"$PANIC_LINES"; then + return 0 + fi + return 1 +} + +assert_no_session_residue() { + residue_label="$1" + residue_session_id="$2" + residue_mountpoint_path="$3" + + if mountpoint -q "$residue_mountpoint_path" 2>/dev/null; then + fail "$residue_label left mount residue at $residue_mountpoint_path" + fi + if ps -e -o pid= -o args= | awk -v id="$residue_session_id" ' + index($0, id) && index($0, "agentfs") && !index($0, "awk -v id=") { + print + found = 1 + } + END { exit found ? 0 : 1 } + ' >/tmp/agentfs-panic-census-ps.$$ 2>/dev/null; then + cat /tmp/agentfs-panic-census-ps.$$ + rm -f /tmp/agentfs-panic-census-ps.$$ + fail "$residue_label left agentfs process residue for $residue_session_id" + fi + rm -f /tmp/agentfs-panic-census-ps.$$ +} + +run_cycle() { + cycle_leg="$1" + cycle_label="$2" + cycle_index="$3" + + home_dir="$TEST_ROOT/home-$cycle_label-$cycle_index" + work_dir="$TEST_ROOT/work-$cycle_label-$cycle_index" + mnt="$work_dir/mnt" + log="$TEST_ROOT/$cycle_label-$cycle_index.log" + id="panic-census-$cycle_label-$cycle_index-$$" + mkdir -p "$home_dir/.cache" "$home_dir/.config" "$work_dir" "$mnt" + + ( + cd "$work_dir" + env HOME="$home_dir" \ + XDG_CACHE_HOME="$home_dir/.cache" \ + XDG_CONFIG_HOME="$home_dir/.config" \ + "$AGENTFS_BIN" init "$id" + ) >/dev/null 2>&1 || fail "$cycle_label cycle $cycle_index init failed" + + ( + cd "$work_dir" + env HOME="$home_dir" \ + XDG_CACHE_HOME="$home_dir/.cache" \ + XDG_CONFIG_HOME="$home_dir/.config" \ + CARGO_HOME="$CARGO_HOME_FOR_TEST" \ + RUSTUP_HOME="$RUSTUP_HOME_FOR_TEST" \ + RUSTUP_TOOLCHAIN="$RUSTUP_TOOLCHAIN_FOR_TEST" \ + AGENTFS_FUSE_URING="$cycle_leg" \ + RUST_LOG=agentfs=info \ + "$AGENTFS_BIN" mount "$id" "$mnt" --backend fuse --foreground + ) >"$log" 2>&1 & + MOUNT_PID=$! + CURRENT_MNT="$mnt" + + if ! wait_for_mount "$mnt"; then + echo + echo "mount log for $cycle_label cycle $cycle_index:" + sed 's/^/ /' "$log" + fail "$cycle_label cycle $cycle_index mount did not become ready" + fi + + start_writer "$mnt" + sleep "$WORKLOAD_SECONDS" + kill -INT "$MOUNT_PID" 2>/dev/null || true + if ! wait_for_mount_exit; then + echo + echo "mount log for $cycle_label cycle $cycle_index:" + sed 's/^/ /' "$log" + fail "$cycle_label cycle $cycle_index did not exit within ${TEARDOWN_TIMEOUT}s after SIGINT" + fi + + if [ -n "$WRITER_PID" ] && kill -0 "$WRITER_PID" 2>/dev/null; then + kill "$WRITER_PID" 2>/dev/null || true + wait "$WRITER_PID" 2>/dev/null || true + fi + WRITER_PID="" + + unmount_current + CURRENT_MNT="" + + if [ "$cycle_leg" = "1" ] && + ! awk '/advertising FUSE_OVER_IO_URING/ { found = 1 } END { exit found ? 0 : 1 }' "$log"; then + echo + echo "mount log for $cycle_label cycle $cycle_index:" + sed 's/^/ /' "$log" + fail "$cycle_label cycle $cycle_index did not advertise FUSE_OVER_IO_URING" + fi + + if [ "$cycle_leg" = "0" ] && + awk '/advertising FUSE_OVER_IO_URING/ { found = 1 } END { exit found ? 0 : 1 }' "$log"; then + echo + echo "mount log for $cycle_label cycle $cycle_index:" + sed 's/^/ /' "$log" + fail "$cycle_label cycle $cycle_index unexpectedly advertised FUSE_OVER_IO_URING" + fi + + scan_panic_lines "$cycle_label-cycle-$cycle_index" "$log" || true + assert_no_session_residue "$cycle_label cycle $cycle_index" "$id" "$mnt" +} + +TEST_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-panic-census.XXXXXX")" +RUN_URING_LEG=1 +URING_CYCLES_RUN=0 +LEGACY_CYCLES_RUN=0 + +if [ ! -r /sys/module/fuse/parameters/enable_uring ]; then + echo "SKIP: uring leg requires readable /sys/module/fuse/parameters/enable_uring" + RUN_URING_LEG=0 +elif [ "$(cat /sys/module/fuse/parameters/enable_uring)" != "Y" ]; then + echo "SKIP: uring leg requires FUSE-over-io_uring enabled by kernel parameter" + RUN_URING_LEG=0 +fi + +for run_leg in 1 0; do + if [ "$run_leg" = "1" ] && [ "$RUN_URING_LEG" -ne 1 ]; then + continue + fi + + case "$run_leg" in + 1) run_label="uring" ;; + 0) run_label="legacy" ;; + esac + run_i=1 + while [ "$run_i" -le "$CYCLES" ]; do + run_cycle "$run_leg" "$run_label" "$run_i" + case "$run_leg" in + 1) URING_CYCLES_RUN=$((URING_CYCLES_RUN + 1)) ;; + 0) LEGACY_CYCLES_RUN=$((LEGACY_CYCLES_RUN + 1)) ;; + esac + run_i=$((run_i + 1)) + done +done + +if [ -s "$PANIC_LINES" ]; then + echo + echo "panic census matched stderr lines:" + cat "$PANIC_LINES" + fail "panic census found Rust panic output" +fi + +printf "OK (uring_cycles=%s legacy_cycles=%s panic_lines=0)\n" \ + "$URING_CYCLES_RUN" "$LEGACY_CYCLES_RUN" diff --git a/crates/agentfs-cli/tests/test-init.sh b/crates/agentfs-cli/tests/test-init.sh new file mode 100755 index 00000000..96aedc15 --- /dev/null +++ b/crates/agentfs-cli/tests/test-init.sh @@ -0,0 +1,63 @@ +#!/bin/sh +set -eu + +echo -n "TEST init... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +TEST_AGENT_ID="test-agent" +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-init.XXXXXX")" + +cleanup() { + rm -rf "$ROOT" +} +trap cleanup EXIT INT TERM + +fail() { + echo "FAILED: $*" + exit 1 +} + +run_agentfs() { + if [ -n "${AGENTFS_BIN:-}" ]; then + "$AGENTFS_BIN" "$@" + else + cargo run --quiet --manifest-path "$CLI_DIR/Cargo.toml" -- "$@" + fi +} + +# agentfs init creates its DB under CWD/.agentfs, so run from the temp root. +cd "$ROOT" + +output=$(run_agentfs init "$TEST_AGENT_ID" 2>&1) || + fail "init command failed +Output was: $output" + +[ -d .agentfs ] || fail ".agentfs directory was not created +Output was: $output" + +[ -f ".agentfs/$TEST_AGENT_ID.db" ] || + fail "agent database was not created in .agentfs directory +Output was: $output" + +echo "$output" | grep -q "Created agent filesystem: .agentfs/$TEST_AGENT_ID.db" || + fail "Expected success message not found in output +Output was: $output" + +# Running init again must fail without --force. +if run_agentfs init "$TEST_AGENT_ID" 2>&1 | grep -q "already exists"; then + : # Expected behavior +else + fail "init should fail when agent database already exists" +fi + +output=$(run_agentfs init "$TEST_AGENT_ID" --force 2>&1) || + fail "init --force command failed +Output was: $output" + +echo "$output" | grep -q "Created agent filesystem: .agentfs/$TEST_AGENT_ID.db" || + fail "Expected success message not found in init --force output +Output was: $output" + +echo "OK" diff --git a/cli/tests/test-linux-syscalls.sh b/crates/agentfs-cli/tests/test-linux-syscalls.sh similarity index 100% rename from cli/tests/test-linux-syscalls.sh rename to crates/agentfs-cli/tests/test-linux-syscalls.sh diff --git a/crates/agentfs-cli/tests/test-mcp-server.sh b/crates/agentfs-cli/tests/test-mcp-server.sh new file mode 100755 index 00000000..90e6addc --- /dev/null +++ b/crates/agentfs-cli/tests/test-mcp-server.sh @@ -0,0 +1,268 @@ +#!/usr/bin/env sh +# +# MCP server regression (D2, M7): +# - tools/list equals the dispatchable tool set exactly (kv_list dispatches, +# phantom help tools rmdir/rm/unlink/copy_file are gone from help) +# - notifications/initialized is accepted without a response (strict clients) +# - every tools/call writes a tool_calls audit row that `agentfs timeline` +# renders, including while the server process is still running (cross-flow) +# - write_file preserves the mode of existing files, creates new files 0644, +# and its data is durable after the server exits +# - unknown tools and unknown methods get proper JSON-RPC errors +# - --tools filters both listing and dispatch; unknown filter names are +# rejected at startup +# - the database family is single-file (no -wal/-shm) once the startup +# validation probe has run and again after graceful shutdown (invariant I1) +set -eu + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +echo -n "TEST mcp server... " + +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-mcp-server.XXXXXX")" + +cleanup() { + rm -rf "$ROOT" +} +trap cleanup EXIT INT TERM + +fail() { + echo "FAILED: $*" + exit 1 +} + +if [ -n "${AGENTFS_BIN:-}" ]; then + BIN="$AGENTFS_BIN" +else + cargo +nightly build --quiet --manifest-path "$CLI_DIR/Cargo.toml" \ + || fail "could not build agentfs" + BIN="$CLI_DIR/../../target/debug/agentfs" +fi +[ -x "$BIN" ] || fail "agentfs binary not found at $BIN" + +cd "$ROOT" +"$BIN" init mcp >/dev/null 2>&1 || fail "init failed" +DB="$ROOT/.agentfs/mcp.db" + +# --- Help must advertise exactly the real tool surface ---------------------- +"$BIN" serve mcp --help >"$ROOT/help.out" 2>&1 || fail "serve mcp --help failed" +for tool in read_file write_file readdir mkdir remove rename stat access \ + kv_get kv_set kv_delete kv_list; do + grep -q "$tool" "$ROOT/help.out" || fail "help does not mention tool $tool" +done +for phantom in rmdir unlink copy_file; do + grep -q "$phantom" "$ROOT/help.out" \ + && fail "help still advertises phantom tool $phantom" +done +grep -Eq '(^|[ ,])rm(,|[ ]|$)' "$ROOT/help.out" \ + && fail "help still advertises phantom tool rm" + +# --- Seed an existing 0755 file through a real mount ------------------------- +HAVE_FUSE=0 +if [ "$(uname -s)" = "Linux" ] && [ -e /dev/fuse ]; then + HAVE_FUSE=1 + "$BIN" exec "$DB" sh -c 'echo "original content" > mode.txt && chmod 755 mode.txt' \ + >/dev/null 2>&1 || fail "seeding mode.txt via exec failed" + "$BIN" exec "$DB" stat -c '%a' mode.txt 2>/dev/null | tail -1 | grep -qx 755 \ + || fail "seeded mode.txt is not 0755" +else + echo "note: FUSE unavailable; mode seeded via MCP stat checks only" +fi + +# --- Main stdio JSON-RPC session --------------------------------------------- +timeout 120 python3 - "$BIN" "$DB" "$HAVE_FUSE" >"$ROOT/session.out" 2>"$ROOT/session.err" <<'EOF' \ + || fail "stdio session failed: $(tail -n 20 -- "$ROOT/session.out" "$ROOT/session.err")" +import json, os, subprocess, sys + +BIN, DB, HAVE_FUSE = sys.argv[1], sys.argv[2], sys.argv[3] == "1" + +def assert_single_file_family(stage): + sidecars = [DB + suffix for suffix in ("-wal", "-shm") + if os.path.exists(DB + suffix)] + assert not sidecars, f"{stage} left sidecars behind: {sidecars}" +EXPECTED_TOOLS = ["read_file", "write_file", "readdir", "mkdir", "remove", + "rename", "stat", "access", "kv_get", "kv_set", "kv_delete", + "kv_list"] + +proc = subprocess.Popen([BIN, "serve", "mcp", DB], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, text=True) + +def send(msg): + proc.stdin.write(json.dumps(msg) + "\n") + proc.stdin.flush() + +def rpc(id_, method, params=None): + send({"jsonrpc": "2.0", "id": id_, "method": method, "params": params or {}}) + resp = json.loads(proc.stdout.readline()) + assert resp.get("id") == id_, f"response id mismatch: {resp}" + return resp + +def result_text(resp): + assert "error" not in resp, f"unexpected error: {resp}" + return resp["result"]["content"][0]["text"] + +resp = rpc(1, "initialize", {"protocolVersion": "2024-11-05", "capabilities": {}}) +assert resp["result"]["protocolVersion"], resp + +# The per-request-mode startup validation probe (open + close) must restore +# the single-file family before the server starts serving: initialize opens +# no database, so any sidecar visible here was left by the probe. +assert_single_file_family("startup validation probe") + +# Strict clients send this right after initialize; the server must accept it +# silently. The next request's response proves nothing was written for it. +send({"jsonrpc": "2.0", "method": "notifications/initialized"}) + +resp = rpc(2, "tools/list") +tools = [tool["name"] for tool in resp["result"]["tools"]] +assert tools == EXPECTED_TOOLS, f"tools/list mismatch: {tools}" + +# Every advertised tool dispatches with minimal valid arguments. +args = { + "write_file": {"path": "/probe.txt", "content": "probe"}, + "read_file": {"path": "/probe.txt"}, + "readdir": {"path": "/"}, + "mkdir": {"path": "/probedir"}, + "remove": {"path": "/probedir"}, + "rename": {"from": "/probe.txt", "to": "/probe2.txt"}, + "stat": {"path": "/probe2.txt"}, + "access": {"path": "/probe2.txt"}, + "kv_set": {"key": "probe", "value": {"n": 1}}, + "kv_get": {"key": "probe"}, + "kv_list": {}, + "kv_delete": {"key": "probe"}, +} +assert sorted(args) == sorted(EXPECTED_TOOLS) +for i, name in enumerate(args, start=10): + result_text(rpc(i, "tools/call", {"name": name, "arguments": args[name]})) + +# write_file preserves an existing file's mode and updates its size. +if HAVE_FUSE: + st = json.loads(result_text(rpc(30, "tools/call", + {"name": "stat", "arguments": {"path": "/mode.txt"}}))) + assert st["mode"] == 0o100755, f"pre-write mode: {st['mode']:o}" + result_text(rpc(31, "tools/call", + {"name": "write_file", "arguments": {"path": "/mode.txt", "content": "overwritten"}})) + st = json.loads(result_text(rpc(32, "tools/call", + {"name": "stat", "arguments": {"path": "/mode.txt"}}))) + assert st["mode"] == 0o100755, f"post-write mode changed: {st['mode']:o}" + assert st["size"] == len("overwritten"), st + +# New files get the documented default mode 0644. +result_text(rpc(33, "tools/call", + {"name": "write_file", "arguments": {"path": "/fresh.txt", "content": "new"}})) +st = json.loads(result_text(rpc(34, "tools/call", + {"name": "stat", "arguments": {"path": "/fresh.txt"}}))) +assert st["mode"] == 0o100644, f"new file mode: {st['mode']:o}" + +# A failing tool call returns an error and is audited as status=error. +resp = rpc(40, "tools/call", {"name": "read_file", "arguments": {"path": "/definitely/missing"}}) +assert "error" in resp, resp + +# Unknown tool -> JSON-RPC invalid-params error naming the tool. +resp = rpc(41, "tools/call", {"name": "not_a_tool", "arguments": {}}) +assert resp["error"]["code"] == -32602, resp +assert "not_a_tool" in resp["error"]["message"], resp + +# Unknown method with an id -> method-not-found. +resp = rpc(42, "bogus/method") +assert resp["error"]["code"] == -32601, resp + +# Cross-flow: while the server process is still running, the CLI timeline +# must see the write_file audit rows. +assert proc.poll() is None, "server exited early" +out = subprocess.run([BIN, "timeline", DB, "--format", "json", "--filter", "write_file"], + capture_output=True, text=True, timeout=60) +assert out.returncode == 0, f"timeline while live failed: {out.stderr}" +rows = json.loads(out.stdout) +live = [row for row in rows if row["name"] == "write_file" and row["status"] == "success"] +assert live, f"no write_file audit rows visible while live: {rows}" + +# The session keeps working after the concurrent timeline read. +result_text(rpc(50, "tools/call", {"name": "access", "arguments": {"path": "/fresh.txt"}})) + +proc.stdin.close() +proc.wait(timeout=15) +assert proc.returncode == 0, f"server exit code {proc.returncode}" + +# Graceful shutdown (stdin EOF) must restore the single-file family the +# per-request opens materialized while serving. +assert_single_file_family("graceful shutdown") +print("SESSION-OK") +EOF +grep -q 'SESSION-OK' "$ROOT/session.out" || fail "stdio session did not complete" + +# --- Audit rows persist and timeline options work after the server exits ---- +"$BIN" timeline "$DB" --format table >"$ROOT/tl-table.out" 2>&1 \ + || fail "timeline table failed" +grep -q 'write_file' "$ROOT/tl-table.out" || fail "timeline table missing write_file" +grep -q 'error' "$ROOT/tl-table.out" || fail "timeline table missing error status" + +"$BIN" timeline "$DB" --format json --limit 1 >"$ROOT/tl-limit.out" 2>&1 \ + || fail "timeline --limit failed" +"$BIN" timeline "$DB" --format json --filter kv_set --status success \ + >"$ROOT/tl-filter.out" 2>&1 || fail "timeline --filter/--status failed" +"$BIN" timeline "$DB" --format json --status error >"$ROOT/tl-error.out" 2>&1 \ + || fail "timeline --status error failed" +python3 - "$ROOT/tl-limit.out" "$ROOT/tl-filter.out" "$ROOT/tl-error.out" <<'EOF' \ + || fail "timeline JSON output malformed" +import json, sys +limited = json.load(open(sys.argv[1])) +assert len(limited) == 1, limited +for field in ("name", "status", "duration_ms", "started_at"): + assert field in limited[0], limited[0] +filtered = json.load(open(sys.argv[2])) +assert filtered and all(r["name"] == "kv_set" and r["status"] == "success" for r in filtered) +errors = json.load(open(sys.argv[3])) +assert any(r["name"] == "not_a_tool" for r in errors), errors +assert any(r["name"] == "read_file" for r in errors), errors +EOF + +# --- Durability: MCP-written data survives the server across processes ------ +if [ "$HAVE_FUSE" = 1 ]; then + "$BIN" exec "$DB" stat -c '%a %s' mode.txt 2>/dev/null | tail -1 \ + | grep -qx '755 11' || fail "mode.txt not '755 11' after server exit" + "$BIN" exec "$DB" cat mode.txt 2>/dev/null | tail -1 | grep -qx 'overwritten' \ + || fail "mode.txt content not durable after server exit" +fi +"$BIN" fs "$DB" cat /fresh.txt >"$ROOT/fresh.out" 2>&1 \ + || fail "fs cat fresh.txt failed" +grep -qx 'new' "$ROOT/fresh.out" || fail "fresh.txt content not durable" + +# --- --tools filters listing and dispatch ------------------------------------ +timeout 60 python3 - "$BIN" "$DB" >"$ROOT/filter.out" 2>&1 <<'EOF' \ + || fail "--tools filter session failed: $(tail -10 "$ROOT/filter.out")" +import json, subprocess, sys +BIN, DB = sys.argv[1], sys.argv[2] +proc = subprocess.Popen([BIN, "serve", "mcp", DB, "--tools", "read_file,kv_list"], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, text=True) +def rpc(id_, method, params): + proc.stdin.write(json.dumps({"jsonrpc": "2.0", "id": id_, "method": method, + "params": params}) + "\n") + proc.stdin.flush() + return json.loads(proc.stdout.readline()) +resp = rpc(1, "tools/list", {}) +tools = [tool["name"] for tool in resp["result"]["tools"]] +assert tools == ["read_file", "kv_list"], tools +resp = rpc(2, "tools/call", {"name": "write_file", + "arguments": {"path": "/filtered.txt", "content": "leak"}}) +assert resp["error"]["code"] == -32602, resp +proc.stdin.close() +proc.wait(timeout=15) +assert proc.returncode == 0 +print("FILTER-OK") +EOF +grep -q 'FILTER-OK' "$ROOT/filter.out" || fail "--tools filter checks did not complete" + +# --- Unknown --tools names are rejected at startup --------------------------- +if "$BIN" serve mcp "$DB" --tools copy_file "$ROOT/badfilter.out" 2>"$ROOT/badfilter.err"; then + fail "--tools copy_file did not fail at startup" +fi +grep -q 'copy_file' "$ROOT/badfilter.err" \ + || fail "startup error does not name the unknown tool: $(cat "$ROOT/badfilter.err")" + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-migrate-consolidation.sh b/crates/agentfs-cli/tests/test-migrate-consolidation.sh new file mode 100755 index 00000000..98fc11da --- /dev/null +++ b/crates/agentfs-cli/tests/test-migrate-consolidation.sh @@ -0,0 +1,224 @@ +#!/usr/bin/env sh +# +# Migrate consolidation regression (D6): +# - one `agentfs migrate ` lands every supported old schema at +# CURRENT in a single invocation and is idempotent +# - open paths (fs ls) reject old schemas with guidance naming that command +# - encrypted old databases migrate in place with --key/--cipher, leaving no +# plaintext copies behind +# - the split `migrate-v0-5` command is gone +# - cross-flow: a migrated old database survives a mounted git workload and +# a clean integrity check +set -eu + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" +FIXTURES="$DIR/fixtures/migrate" + +# Kept in step with tests/migrate_fixtures.rs. +ENC_KEY="00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff" +ENC_CIPHER="aes256gcm" + +echo -n "TEST migrate consolidation... " + +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-migrate-consolidation.XXXXXX")" +MNT="$ROOT/mnt" +PINNED_TMP="$ROOT/pinned-tmp" +mkdir -p "$MNT" "$PINNED_TMP" "$ROOT/home" +MOUNT_PID="" + +cleanup() { + if [ -n "$MOUNT_PID" ]; then + kill "$MOUNT_PID" 2>/dev/null || true + fi + if mountpoint -q "$MNT" 2>/dev/null; then + fusermount3 -u "$MNT" >/dev/null 2>&1 || fusermount -u "$MNT" >/dev/null 2>&1 || true + fi + rm -rf "$ROOT" +} +trap cleanup EXIT INT TERM + +fail() { + echo "FAILED: $*" + exit 1 +} + +# Resolve the binary once: background legs must track the agentfs PID itself +# (backgrounding a wrapper orphans the real process on cleanup kill). +if [ -n "${AGENTFS_BIN:-}" ]; then + BIN="$AGENTFS_BIN" +else + cargo +nightly build --quiet --manifest-path "$CLI_DIR/Cargo.toml" || { + echo "FAILED: could not build agentfs" + exit 1 + } + BIN="$CLI_DIR/../../target/debug/agentfs" +fi +if [ ! -x "$BIN" ]; then + echo "FAILED: agentfs binary not found at $BIN" + exit 1 +fi + +run_agentfs() { + "$BIN" "$@" +} + +user_version_of() { + python3 - "$1" <<'EOF' +import sqlite3, sys +conn = sqlite3.connect(f"file:{sys.argv[1]}?mode=ro", uri=True) +print(conn.execute("PRAGMA user_version").fetchone()[0]) +conn.close() +EOF +} + +[ -f "$FIXTURES/v0_0.db" ] || fail "missing committed fixtures under $FIXTURES" + +# --- Obsolete split command is gone ----------------------------------------- +if run_agentfs migrate-v0-5 --help >"$ROOT/mv5.out" 2>&1; then + fail "migrate-v0-5 still exists as a command" +fi +if run_agentfs --help 2>/dev/null | grep -q 'migrate-v0-5'; then + fail "--help still advertises migrate-v0-5" +fi + +# --- One migrate per supported old schema ----------------------------------- +for name in v0_0 v0_2 v0_4; do + DB="$ROOT/$name.db" + cp "$FIXTURES/$name.db" "$DB" + + # Open paths must refuse the old schema and name the command that fixes it. + if run_agentfs fs "$DB" ls / >"$ROOT/$name-ls-before.out" 2>"$ROOT/$name-ls-before.err"; then + fail "$name: fs ls opened an old-schema database without migration" + fi + grep -q "agentfs migrate $DB" "$ROOT/$name-ls-before.err" \ + || fail "$name: schema-mismatch guidance does not name 'agentfs migrate $DB' (stderr: $(cat "$ROOT/$name-ls-before.err"))" + grep -q 'migrate-v0-5' "$ROOT/$name-ls-before.err" \ + && fail "$name: guidance mentions the deleted migrate-v0-5 command" + + run_agentfs migrate "$DB" >"$ROOT/$name-migrate.out" 2>&1 \ + || fail "$name: migrate failed: $(cat "$ROOT/$name-migrate.out")" + grep -q 'Migration completed successfully.' "$ROOT/$name-migrate.out" \ + || fail "$name: migrate output missing completion line" + grep -q 'Target schema version: 0.5 (CURRENT)' "$ROOT/$name-migrate.out" \ + || fail "$name: migrate output missing CURRENT target line" + + UV="$(user_version_of "$DB")" + [ "$UV" = "5" ] || fail "$name: user_version after migrate is $UV, expected 5" + + # Idempotent second run. + run_agentfs migrate "$DB" >"$ROOT/$name-migrate2.out" 2>&1 \ + || fail "$name: second migrate failed" + grep -q 'Database is already at schema 0.5.' "$ROOT/$name-migrate2.out" \ + || fail "$name: second migrate is not idempotent: $(cat "$ROOT/$name-migrate2.out")" + + run_agentfs integrity "$DB" >"$ROOT/$name-integrity.out" 2>&1 \ + || fail "$name: integrity failed after migrate: $(cat "$ROOT/$name-integrity.out")" + run_agentfs fs "$DB" ls / >"$ROOT/$name-ls-after.out" 2>&1 \ + || fail "$name: fs ls failed after migrate" + grep -q 'small.txt' "$ROOT/$name-ls-after.out" \ + || fail "$name: migrated filesystem lost its contents" +done + +# --- Encrypted old database migrates with --key/--cipher -------------------- +ENC_DB="$ROOT/v0_4-encrypted.db" +cp "$FIXTURES/v0_4-encrypted.db" "$ENC_DB" +( + export TMPDIR="$PINNED_TMP" TMP="$PINNED_TMP" TEMP="$PINNED_TMP" + run_agentfs migrate "$ENC_DB" --key "$ENC_KEY" --cipher "$ENC_CIPHER" +) >"$ROOT/enc-migrate.out" 2>&1 \ + || fail "encrypted migrate failed: $(cat "$ROOT/enc-migrate.out")" +grep -q 'Migration completed successfully.' "$ROOT/enc-migrate.out" \ + || fail "encrypted migrate output missing completion line" + +LEFTOVER="$(find "$PINNED_TMP" -mindepth 1 | head -5)" +[ -z "$LEFTOVER" ] || fail "encrypted migrate left temp artifacts: $LEFTOVER" +STRAYS="$(find "$ROOT" -maxdepth 1 -name 'v0_4-encrypted.db*' ! -name 'v0_4-encrypted.db' | head -5)" +[ -z "$STRAYS" ] || fail "encrypted migrate left sidecar/copy artifacts: $STRAYS" + +run_agentfs fs --key "$ENC_KEY" --cipher "$ENC_CIPHER" "$ENC_DB" ls / \ + >"$ROOT/enc-ls.out" 2>&1 || fail "encrypted fs ls with key failed" +grep -q 'small.txt' "$ROOT/enc-ls.out" || fail "encrypted migrated fs lost its contents" +if run_agentfs fs "$ENC_DB" ls / >/dev/null 2>&1; then + fail "encrypted database opened without the key" +fi +run_agentfs integrity "$ENC_DB" --key "$ENC_KEY" --cipher "$ENC_CIPHER" >/dev/null 2>&1 \ + || fail "encrypted integrity with key failed" + +# --- Cross-flow: migrated old DB survives a mounted git workload ------------ +case "$(uname -s)" in + Linux) + if [ ! -e /dev/fuse ]; then + echo "OK (mount leg skipped: no /dev/fuse)" + exit 0 + fi + ;; + *) + echo "OK (mount leg skipped: requires Linux FUSE)" + exit 0 + ;; +esac + +WORK_DB="$ROOT/v0_4.db" # migrated above +"$BIN" mount "$WORK_DB" "$MNT" --foreground >"$ROOT/mount.log" 2>&1 & +MOUNT_PID=$! + +WAITED=0 +while [ $WAITED -lt 40 ]; do + if mountpoint -q "$MNT" 2>/dev/null; then + break + fi + sleep 0.25 + WAITED=$((WAITED + 1)) +done +mountpoint -q "$MNT" || fail "migrated database did not mount: $(tail -5 "$ROOT/mount.log")" + +[ "$(cat "$MNT/dir/small.txt")" = "hello fixture" ] \ + || fail "migrated data not readable through the mount" + +( + set -e + cd "$MNT" + mkdir -p repo && cd repo + export HOME="$ROOT/home" GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null + # Pin the distro git: the user's PATH shim daemonizes hook managers out + # of test repos (library/environment.md). + for candidate in /usr/bin/git /bin/git; do + if [ -x "$candidate" ]; then + mkdir -p "$ROOT/home/bin" + ln -sf "$candidate" "$ROOT/home/bin/git" + PATH="$ROOT/home/bin:$PATH" + export PATH + break + fi + done + git init -q . + git config user.email agentfs@example.invalid + git config user.name AgentFS + printf 'one\n' > a.txt + printf 'two\n' > b.txt + git add . + git commit -q -m 'migrated workload' + [ -z "$(git status --porcelain)" ] + git fsck --strict --no-progress +) >"$ROOT/git.log" 2>&1 || fail "git workload on migrated mount failed: $(tail -10 "$ROOT/git.log")" + +fusermount3 -u "$MNT" 2>/dev/null || fusermount -u "$MNT" 2>/dev/null \ + || fail "unmount failed" +WAITED=0 +while kill -0 "$MOUNT_PID" 2>/dev/null && [ $WAITED -lt 40 ]; do + sleep 0.25 + WAITED=$((WAITED + 1)) +done +kill -0 "$MOUNT_PID" 2>/dev/null && fail "mount owner did not exit after unmount" +MOUNT_PID="" + +run_agentfs integrity "$WORK_DB" --json >"$ROOT/final-integrity.json" 2>"$ROOT/final-integrity.err" \ + || fail "integrity after mounted workload failed: $(cat "$ROOT/final-integrity.err")" +python3 - "$ROOT/final-integrity.json" <<'EOF' || fail "integrity --json did not report ok:true" +import json, sys +report = json.load(open(sys.argv[1])) +assert report["ok"] is True, report +EOF + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-mount.sh b/crates/agentfs-cli/tests/test-mount.sh new file mode 100755 index 00000000..dccfab41 --- /dev/null +++ b/crates/agentfs-cli/tests/test-mount.sh @@ -0,0 +1,208 @@ +#!/bin/sh +set -eu + +echo -n "TEST mount... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +TEST_AGENT_ID="test-mount-agent" +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-mount.XXXXXX")" +MOUNTPOINT="$ROOT/mnt" +CYCLE_MOUNTPOINT="$ROOT/mnt-cycle" +MOUNT_PID="" +CYCLE_PIDS="" + +unmount_quiet() { + fusermount3 -u "$MOUNTPOINT" 2>/dev/null || + fusermount -u "$MOUNTPOINT" 2>/dev/null || true +} + +cleanup() { + unmount_quiet + for cycle_mnt in "$CYCLE_MOUNTPOINT".*; do + if mountpoint -q "$cycle_mnt" 2>/dev/null; then + fusermount3 -u "$cycle_mnt" 2>/dev/null || true + fi + done + for pid in $MOUNT_PID $CYCLE_PIDS; do + kill "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + done + rm -rf "$ROOT" +} +trap cleanup EXIT INT TERM + +fail() { + echo "FAILED: $*" + exit 1 +} + +# Resolve the binary once: background mount legs must track the agentfs PID +# itself (backgrounding a wrapper orphans the real process on cleanup kill). +if [ -n "${AGENTFS_BIN:-}" ]; then + BIN="$AGENTFS_BIN" +else + cargo build --quiet --manifest-path "$CLI_DIR/Cargo.toml" || { + echo "FAILED: could not build agentfs" + exit 1 + } + BIN="$CLI_DIR/../../target/debug/agentfs" +fi +if [ ! -x "$BIN" ]; then + echo "FAILED: agentfs binary not found at $BIN" + exit 1 +fi + +run_agentfs() { + "$BIN" "$@" +} + +# The session DB lands under $ROOT/.agentfs instead of the repo working tree. +cd "$ROOT" + +run_agentfs init "$TEST_AGENT_ID" > /dev/null 2>&1 + +mkdir -p "$MOUNTPOINT" + +# Mount in foreground mode (background it ourselves so we can control it) +"$BIN" mount ".agentfs/${TEST_AGENT_ID}.db" "$MOUNTPOINT" --foreground & +MOUNT_PID=$! + +# Wait for mount to be ready +MAX_WAIT=20 +WAITED=0 +while [ $WAITED -lt $MAX_WAIT ]; do + if mountpoint -q "$MOUNTPOINT" 2>/dev/null; then + break + fi + sleep 0.5 + WAITED=$((WAITED + 1)) +done + +mountpoint -q "$MOUNTPOINT" 2>/dev/null || fail "mount did not become ready in time" + +# Test that 'agentfs mount' (no args) lists our mount +run_agentfs mount 2>/dev/null | grep -q "$MOUNTPOINT" || + fail "'agentfs mount' did not list our mountpoint" + +# Write a file through the FUSE mount +echo "hello from fuse mount" > "$MOUNTPOINT/hello.txt" + +# Read it back +CONTENT=$(cat "$MOUNTPOINT/hello.txt") + +[ "$CONTENT" = "hello from fuse mount" ] || fail "file content mismatch +Expected: hello from fuse mount +Got: $CONTENT" + +# Test mkdir +mkdir "$MOUNTPOINT/testdir" +[ -d "$MOUNTPOINT/testdir" ] || fail "mkdir did not create directory" + +# Test creating file in subdirectory +echo "nested file" > "$MOUNTPOINT/testdir/nested.txt" +NESTED_CONTENT=$(cat "$MOUNTPOINT/testdir/nested.txt") + +[ "$NESTED_CONTENT" = "nested file" ] || fail "nested file content mismatch" + +# Test symlink creation +ln -s nested.txt "$MOUNTPOINT/testdir/link_to_nested" +[ -L "$MOUNTPOINT/testdir/link_to_nested" ] || fail "symlink was not created" + +# Test reading symlink target +LINK_TARGET=$(readlink "$MOUNTPOINT/testdir/link_to_nested") +[ "$LINK_TARGET" = "nested.txt" ] || fail "symlink target mismatch +Expected: nested.txt +Got: $LINK_TARGET" + +# Test following symlink to read file +LINKED_CONTENT=$(cat "$MOUNTPOINT/testdir/link_to_nested") +[ "$LINKED_CONTENT" = "nested file" ] || fail "reading through symlink failed +Expected: nested file +Got: $LINKED_CONTENT" + +# Test symlink to directory +ln -s testdir "$MOUNTPOINT/link_to_testdir" +[ -L "$MOUNTPOINT/link_to_testdir" ] || fail "symlink to directory was not created" + +# Test accessing file through directory symlink +DIR_LINKED_CONTENT=$(cat "$MOUNTPOINT/link_to_testdir/nested.txt") +[ "$DIR_LINKED_CONTENT" = "nested file" ] || fail "reading through directory symlink failed" + +# Unmount +unmount_quiet + +# Wait for mount process to exit +wait $MOUNT_PID 2>/dev/null || true +MOUNT_PID="" + +# Rapid unmount-then-mount cycles: each remount starts while the previous +# owner is still tearing down, racing the kernel-side drain of the just-closed +# FUSE connection. With fuse-over-io_uring enabled that race used to wedge +# mount(2) forever; it must now either become ready or exit nonzero within a +# bounded window (docs/MANUAL.md, "FUSE-over-io_uring and rapid remounts"). +# One fresh mountpoint and database per cycle, like the back-to-back suite +# tests that originally hit the wedge. This leg also regresses the recycled +# fusectl-id abort: a prior owner's teardown must not abort the fresh mount +# that inherited its connection id (silent mount-then-exit-0 pre-fix). +CYCLE_WINDOW="${MOUNT_CYCLE_WINDOW:-25}" +for cycle in 1 2 3; do + CYCLE_MNT="$CYCLE_MOUNTPOINT.$cycle" + mkdir -p "$CYCLE_MNT" + run_agentfs init "cycle-$cycle" > /dev/null 2>&1 + "$BIN" mount ".agentfs/cycle-$cycle.db" "$CYCLE_MNT" --foreground \ + >"$ROOT/cycle.$cycle.log" 2>&1 & + CYCLE_PID=$! + CYCLE_PIDS="$CYCLE_PIDS $CYCLE_PID" + T0=$(date +%s) + OUTCOME="" + while [ $(( $(date +%s) - T0 )) -le "$CYCLE_WINDOW" ]; do + if mountpoint -q "$CYCLE_MNT" 2>/dev/null; then + OUTCOME="mounted" + break + fi + if ! kill -0 "$CYCLE_PID" 2>/dev/null; then + OUTCOME="exited" + break + fi + sleep 0.1 + done + if [ -z "$OUTCOME" ]; then + echo "cycle $cycle log:" + sed 's/^/ /' "$ROOT/cycle.$cycle.log" || true + fail "rapid remount cycle $cycle wedged: not mounted and still running after ${CYCLE_WINDOW}s" + fi + if [ "$OUTCOME" = "mounted" ]; then + # Immediately unmount and start the next cycle without reaping, + # keeping the drain overlap that triggers the race. + fusermount3 -u "$CYCLE_MNT" 2>/dev/null || + fusermount -u "$CYCLE_MNT" 2>/dev/null || + fail "rapid remount cycle $cycle could not unmount" + else + RC=0 + wait "$CYCLE_PID" 2>/dev/null || RC=$? + [ "$RC" -ne 0 ] || fail "rapid remount cycle $cycle exited 0 without mounting" + echo "note: rapid remount cycle $cycle failed bounded with rc=$RC (accepted: clear error beats a wedge)" + sed 's/^/ /' "$ROOT/cycle.$cycle.log" || true + fi +done + +# Suite invariant: reap every mount owner before returning so no FUSE +# connection drain overlaps the next test's mount. +for pid in $CYCLE_PIDS; do + WAITED=0 + while kill -0 "$pid" 2>/dev/null && [ "$WAITED" -lt 100 ]; do + sleep 0.1 + WAITED=$((WAITED + 1)) + done + if kill -0 "$pid" 2>/dev/null; then + kill "$pid" 2>/dev/null || true + sleep 0.5 + kill -0 "$pid" 2>/dev/null && fail "rapid remount owner $pid did not exit" + fi + wait "$pid" 2>/dev/null || true +done +CYCLE_PIDS="" + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-overlay-base-dir-rename-exdev.sh b/crates/agentfs-cli/tests/test-overlay-base-dir-rename-exdev.sh new file mode 100755 index 00000000..13f7fe1e --- /dev/null +++ b/crates/agentfs-cli/tests/test-overlay-base-dir-rename-exdev.sh @@ -0,0 +1,194 @@ +#!/bin/sh +set -eu + +echo -n "TEST overlay base directory rename EXDEV fallback... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +TEST_AGENT_ID="test-overlay-base-dir-rename-agent" +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-overlay-rename.XXXXXX")" +MOUNTPOINT="$ROOT/mnt" +BASEDIR="$ROOT/base" +MOUNT_LOG="$ROOT/mount.log" + +cleanup() { + fusermount3 -u "$MOUNTPOINT" 2>/dev/null || fusermount -u "$MOUNTPOINT" 2>/dev/null || true + if [ "${MOUNT_PID:-}" ]; then + wait "$MOUNT_PID" 2>/dev/null || true + fi + rm -rf "$ROOT" 2>/dev/null || true +} + +trap cleanup EXIT INT TERM + +# Resolve the binary once: background mount legs must track the agentfs PID +# itself (backgrounding a wrapper orphans the real process on cleanup kill). +if [ -n "${AGENTFS_BIN:-}" ]; then + BIN="$AGENTFS_BIN" +else + cargo build --quiet --manifest-path "$CLI_DIR/Cargo.toml" || { + echo "FAILED: could not build agentfs" + exit 1 + } + BIN="$CLI_DIR/../../target/debug/agentfs" +fi +if [ ! -x "$BIN" ]; then + echo "FAILED: agentfs binary not found at $BIN" + exit 1 +fi + +run_agentfs() { + "$BIN" "$@" +} + +# The session DB lands under $ROOT/.agentfs instead of the repo working tree. +cd "$ROOT" + +mkdir -p \ + "$BASEDIR/rename_probe/sub" \ + "$BASEDIR/mv_src/sub" \ + "$BASEDIR/merged_probe/sub" \ + "$BASEDIR/merged_mv_src/sub" \ + "$MOUNTPOINT" +printf "probe root\n" > "$BASEDIR/rename_probe/root.txt" +printf "probe nested\n" > "$BASEDIR/rename_probe/sub/nested.txt" +printf "mv root\n" > "$BASEDIR/mv_src/root.txt" +printf "mv nested\n" > "$BASEDIR/mv_src/sub/nested.txt" +printf "merged probe root\n" > "$BASEDIR/merged_probe/root.txt" +printf "merged probe nested\n" > "$BASEDIR/merged_probe/sub/nested.txt" +printf "merged mv root\n" > "$BASEDIR/merged_mv_src/root.txt" +printf "merged mv nested\n" > "$BASEDIR/merged_mv_src/sub/nested.txt" + +if ! output=$(run_agentfs init "$TEST_AGENT_ID" --base "$BASEDIR" 2>&1); then + echo "FAILED: init with --base failed" + echo "Output was: $output" + exit 1 +fi + +"$BIN" mount ".agentfs/${TEST_AGENT_ID}.db" "$MOUNTPOINT" --foreground >"$MOUNT_LOG" 2>&1 & +MOUNT_PID=$! + +MAX_WAIT=10 +WAITED=0 +while [ "$WAITED" -lt "$MAX_WAIT" ]; do + if mountpoint -q "$MOUNTPOINT" 2>/dev/null; then + break + fi + sleep 0.5 + WAITED=$((WAITED + 1)) +done + +if ! mountpoint -q "$MOUNTPOINT" 2>/dev/null; then + echo "FAILED: mount did not become ready in time" + cat "$MOUNT_LOG" 2>/dev/null || true + exit 1 +fi + +python3 - "$MOUNTPOINT/rename_probe" "$MOUNTPOINT/rename_probe_moved" <<'PY' +import errno +import os +import sys + +src, dst = sys.argv[1], sys.argv[2] +try: + os.rename(src, dst) +except OSError as exc: + if exc.errno == errno.EXDEV: + raise SystemExit(0) + print(f"expected EXDEV from os.rename, got errno={exc.errno}: {exc}", file=sys.stderr) + raise SystemExit(1) +else: + print("expected EXDEV from os.rename, but rename succeeded", file=sys.stderr) + raise SystemExit(1) +PY + +if [ ! -f "$MOUNTPOINT/rename_probe/root.txt" ] || [ ! -f "$MOUNTPOINT/rename_probe/sub/nested.txt" ]; then + echo "FAILED: source tree changed after EXDEV rename" + exit 1 +fi +if [ -e "$MOUNTPOINT/rename_probe_moved" ]; then + echo "FAILED: destination exists after EXDEV rename" + exit 1 +fi + +mv "$MOUNTPOINT/mv_src" "$MOUNTPOINT/mv_dst" + +if [ -e "$MOUNTPOINT/mv_src" ]; then + echo "FAILED: mv fallback left old overlay path visible" + exit 1 +fi +if [ ! -f "$MOUNTPOINT/mv_dst/root.txt" ] || [ ! -f "$MOUNTPOINT/mv_dst/sub/nested.txt" ]; then + echo "FAILED: mv fallback did not create complete destination tree" + exit 1 +fi +cmp "$MOUNTPOINT/mv_dst/root.txt" "$BASEDIR/mv_src/root.txt" +cmp "$MOUNTPOINT/mv_dst/sub/nested.txt" "$BASEDIR/mv_src/sub/nested.txt" + +if [ ! -f "$BASEDIR/mv_src/root.txt" ] || [ ! -f "$BASEDIR/mv_src/sub/nested.txt" ]; then + echo "FAILED: base backing tree was modified" + exit 1 +fi + +printf "delta probe\n" > "$MOUNTPOINT/merged_probe/new.txt" + +python3 - "$MOUNTPOINT/merged_probe" "$MOUNTPOINT/merged_probe_moved" <<'PY' +import errno +import os +import sys + +src, dst = sys.argv[1], sys.argv[2] +try: + os.rename(src, dst) +except OSError as exc: + if exc.errno == errno.EXDEV: + raise SystemExit(0) + print(f"expected EXDEV from merged os.rename, got errno={exc.errno}: {exc}", file=sys.stderr) + raise SystemExit(1) +else: + print("expected EXDEV from merged os.rename, but rename succeeded", file=sys.stderr) + raise SystemExit(1) +PY + +if [ ! -f "$MOUNTPOINT/merged_probe/root.txt" ] \ + || [ ! -f "$MOUNTPOINT/merged_probe/sub/nested.txt" ] \ + || [ ! -f "$MOUNTPOINT/merged_probe/new.txt" ]; then + echo "FAILED: merged source tree changed after EXDEV rename" + exit 1 +fi +if [ -e "$MOUNTPOINT/merged_probe_moved" ]; then + echo "FAILED: merged destination exists after EXDEV rename" + exit 1 +fi + +printf "merged delta\n" > "$MOUNTPOINT/merged_mv_src/new.txt" +mv "$MOUNTPOINT/merged_mv_src" "$MOUNTPOINT/merged_mv_dst" + +if [ -e "$MOUNTPOINT/merged_mv_src" ]; then + echo "FAILED: merged mv fallback left old overlay path visible" + exit 1 +fi +if [ ! -f "$MOUNTPOINT/merged_mv_dst/root.txt" ] \ + || [ ! -f "$MOUNTPOINT/merged_mv_dst/sub/nested.txt" ] \ + || [ ! -f "$MOUNTPOINT/merged_mv_dst/new.txt" ]; then + echo "FAILED: merged mv fallback did not create complete destination tree" + exit 1 +fi +cmp "$MOUNTPOINT/merged_mv_dst/root.txt" "$BASEDIR/merged_mv_src/root.txt" +cmp "$MOUNTPOINT/merged_mv_dst/sub/nested.txt" "$BASEDIR/merged_mv_src/sub/nested.txt" +if [ "$(cat "$MOUNTPOINT/merged_mv_dst/new.txt")" != "merged delta" ]; then + echo "FAILED: merged mv fallback did not preserve delta child" + exit 1 +fi + +if [ ! -f "$BASEDIR/merged_mv_src/root.txt" ] \ + || [ ! -f "$BASEDIR/merged_mv_src/sub/nested.txt" ]; then + echo "FAILED: merged base backing tree was modified" + exit 1 +fi + +fusermount3 -u "$MOUNTPOINT" 2>/dev/null || fusermount -u "$MOUNTPOINT" +wait "$MOUNT_PID" 2>/dev/null || true +MOUNT_PID= + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-overlay-delta-in-base-dir.sh b/crates/agentfs-cli/tests/test-overlay-delta-in-base-dir.sh new file mode 100755 index 00000000..45a47fda --- /dev/null +++ b/crates/agentfs-cli/tests/test-overlay-delta-in-base-dir.sh @@ -0,0 +1,144 @@ +#!/bin/sh +set -eu + +echo -n "TEST overlay readdir/unlink for delta files in base directories... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +TEST_AGENT_ID="test-overlay-delta-in-base-dir-agent" +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-overlay-delta.XXXXXX")" +MOUNTPOINT="$ROOT/mnt" +BASEDIR="$ROOT/base" +MOUNT_PID="" + +unmount_quiet() { + fusermount3 -u "$MOUNTPOINT" 2>/dev/null || + fusermount -u "$MOUNTPOINT" 2>/dev/null || true +} + +cleanup() { + unmount_quiet + if [ -n "$MOUNT_PID" ]; then + kill "$MOUNT_PID" 2>/dev/null || true + wait "$MOUNT_PID" 2>/dev/null || true + fi + rm -rf "$ROOT" +} +trap cleanup EXIT INT TERM + +fail() { + echo "FAILED: $*" + exit 1 +} + +# Resolve the binary once: background mount legs must track the agentfs PID +# itself (backgrounding a wrapper orphans the real process on cleanup kill). +if [ -n "${AGENTFS_BIN:-}" ]; then + BIN="$AGENTFS_BIN" +else + cargo build --quiet --manifest-path "$CLI_DIR/Cargo.toml" || { + echo "FAILED: could not build agentfs" + exit 1 + } + BIN="$CLI_DIR/../../target/debug/agentfs" +fi +if [ ! -x "$BIN" ]; then + echo "FAILED: agentfs binary not found at $BIN" + exit 1 +fi + +run_agentfs() { + "$BIN" "$@" +} + +# The session DB lands under $ROOT/.agentfs instead of the repo working tree. +cd "$ROOT" + +# Create base directory with a subdirectory (simulating .git) +mkdir -p "$BASEDIR/.git" +echo "[core]" > "$BASEDIR/.git/config" +echo "ref: refs/heads/main" > "$BASEDIR/.git/HEAD" + +# Initialize the database with --base for overlay +output=$(run_agentfs init "$TEST_AGENT_ID" --base "$BASEDIR" 2>&1) || + fail "init with --base failed +Output was: $output" + +mkdir -p "$MOUNTPOINT" + +# Mount in foreground mode (background it ourselves so we can control it) +"$BIN" mount ".agentfs/${TEST_AGENT_ID}.db" "$MOUNTPOINT" --foreground & +MOUNT_PID=$! + +# Wait for mount to be ready +MAX_WAIT=20 +WAITED=0 +while [ $WAITED -lt $MAX_WAIT ]; do + if mountpoint -q "$MOUNTPOINT" 2>/dev/null; then + break + fi + sleep 0.5 + WAITED=$((WAITED + 1)) +done + +mountpoint -q "$MOUNTPOINT" 2>/dev/null || fail "mount did not become ready in time" + +# Verify base directory structure is visible +[ -d "$MOUNTPOINT/.git" ] || fail "base .git directory not visible through overlay" +[ -f "$MOUNTPOINT/.git/config" ] || fail "base .git/config file not visible through overlay" + +# Create a new file in the base subdirectory through the overlay +# This triggers ensure_parent_dirs which creates .git in delta with origin mapping +echo "lock content" > "$MOUNTPOINT/.git/index.lock" + +# Verify the file was created +[ -f "$MOUNTPOINT/.git/index.lock" ] || fail "could not create index.lock in .git directory" + +# Verify readdir shows both base and delta files +# This is the first bug: delta files in base directories were invisible in readdir +LS_OUTPUT=$(ls "$MOUNTPOINT/.git") +echo "$LS_OUTPUT" | grep -q "index.lock" || fail "readdir does not show delta file index.lock +ls output was: $LS_OUTPUT" +echo "$LS_OUTPUT" | grep -q "config" || fail "readdir does not show base file config +ls output was: $LS_OUTPUT" +echo "$LS_OUTPUT" | grep -q "HEAD" || fail "readdir does not show base file HEAD +ls output was: $LS_OUTPUT" + +# Delete the delta file +# This is the second bug: unlink failed for delta files in base directories +rm "$MOUNTPOINT/.git/index.lock" + +# Verify the file is actually deleted +[ ! -f "$MOUNTPOINT/.git/index.lock" ] || fail "index.lock still exists after deletion" + +# Verify readdir no longer shows it +LS_OUTPUT_AFTER=$(ls "$MOUNTPOINT/.git") +if echo "$LS_OUTPUT_AFTER" | grep -q "index.lock"; then + fail "readdir still shows index.lock after deletion +ls output was: $LS_OUTPUT_AFTER" +fi + +# Base files should still be visible +echo "$LS_OUTPUT_AFTER" | grep -q "config" || + fail "base file config disappeared after delta file deletion" + +# Test creating and deleting a subdirectory in a base directory +mkdir "$MOUNTPOINT/.git/objects" +[ -d "$MOUNTPOINT/.git/objects" ] || fail "could not create objects subdirectory in .git" + +# Verify readdir shows the new directory +LS_WITH_DIR=$(ls "$MOUNTPOINT/.git") +echo "$LS_WITH_DIR" | grep -q "objects" || fail "readdir does not show delta directory objects +ls output was: $LS_WITH_DIR" + +# Remove the directory (rmdir) +rmdir "$MOUNTPOINT/.git/objects" +[ ! -d "$MOUNTPOINT/.git/objects" ] || fail "objects directory still exists after rmdir" + +# Unmount +unmount_quiet +wait $MOUNT_PID 2>/dev/null || true +MOUNT_PID="" + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-overlay-whiteout.sh b/crates/agentfs-cli/tests/test-overlay-whiteout.sh new file mode 100755 index 00000000..a46cd59e --- /dev/null +++ b/crates/agentfs-cli/tests/test-overlay-whiteout.sh @@ -0,0 +1,124 @@ +#!/bin/sh +set -eu + +echo -n "TEST overlay whiteout persistence... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +TEST_AGENT_ID="test-overlay-whiteout-agent" +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-overlay-whiteout.XXXXXX")" +MOUNTPOINT="$ROOT/mnt" +BASEDIR="$ROOT/base" +MOUNT_PID="" + +unmount_quiet() { + fusermount3 -u "$MOUNTPOINT" 2>/dev/null || + fusermount -u "$MOUNTPOINT" 2>/dev/null || true +} + +cleanup() { + unmount_quiet + if [ -n "$MOUNT_PID" ]; then + kill "$MOUNT_PID" 2>/dev/null || true + wait "$MOUNT_PID" 2>/dev/null || true + fi + rm -rf "$ROOT" +} +trap cleanup EXIT INT TERM + +fail() { + echo "FAILED: $*" + exit 1 +} + +# Resolve the binary once: background mount legs must track the agentfs PID +# itself (backgrounding a wrapper orphans the real process on cleanup kill). +if [ -n "${AGENTFS_BIN:-}" ]; then + BIN="$AGENTFS_BIN" +else + cargo build --quiet --manifest-path "$CLI_DIR/Cargo.toml" || { + echo "FAILED: could not build agentfs" + exit 1 + } + BIN="$CLI_DIR/../../target/debug/agentfs" +fi +if [ ! -x "$BIN" ]; then + echo "FAILED: agentfs binary not found at $BIN" + exit 1 +fi + +run_agentfs() { + "$BIN" "$@" +} + +wait_mounted() { + WAITED=0 + while [ $WAITED -lt 20 ]; do + if mountpoint -q "$MOUNTPOINT" 2>/dev/null; then + return 0 + fi + sleep 0.5 + WAITED=$((WAITED + 1)) + done + mountpoint -q "$MOUNTPOINT" 2>/dev/null +} + +# The session DB lands under $ROOT/.agentfs instead of the repo working tree. +cd "$ROOT" + +# Create base directory with a test file +mkdir -p "$BASEDIR" +echo "original content" > "$BASEDIR/testfile.txt" + +# Initialize the database with --base for overlay +output=$(run_agentfs init "$TEST_AGENT_ID" --base "$BASEDIR" 2>&1) || + fail "init with --base failed +Output was: $output" + +mkdir -p "$MOUNTPOINT" + +# Mount in foreground mode (background it ourselves so we can control it) +"$BIN" mount ".agentfs/${TEST_AGENT_ID}.db" "$MOUNTPOINT" --foreground & +MOUNT_PID=$! + +wait_mounted || fail "mount did not become ready in time" + +# Verify base file is visible through overlay +[ -f "$MOUNTPOINT/testfile.txt" ] || fail "base file not visible through overlay" + +CONTENT=$(cat "$MOUNTPOINT/testfile.txt") +[ "$CONTENT" = "original content" ] || fail "base file content mismatch +Expected: original content +Got: $CONTENT" + +# Delete the file through the overlay +rm "$MOUNTPOINT/testfile.txt" + +# Verify file is deleted +[ ! -f "$MOUNTPOINT/testfile.txt" ] || fail "file still exists after deletion" + +# Unmount +unmount_quiet +wait $MOUNT_PID 2>/dev/null || true +MOUNT_PID="" + +# Remount to test persistence +"$BIN" mount ".agentfs/${TEST_AGENT_ID}.db" "$MOUNTPOINT" --foreground & +MOUNT_PID=$! + +wait_mounted || fail "remount did not become ready in time" + +# Verify file is still deleted after remount (whiteout was persisted) +[ ! -f "$MOUNTPOINT/testfile.txt" ] || + fail "deleted file reappeared after remount (whiteout not persisted)" + +# Verify base file still exists in original location (untouched) +[ -f "$BASEDIR/testfile.txt" ] || fail "base file was modified (should be untouched)" + +# Unmount +unmount_quiet +wait $MOUNT_PID 2>/dev/null || true +MOUNT_PID="" + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-profile-error-summary.sh b/crates/agentfs-cli/tests/test-profile-error-summary.sh new file mode 100755 index 00000000..5a6822bb --- /dev/null +++ b/crates/agentfs-cli/tests/test-profile-error-summary.sh @@ -0,0 +1,70 @@ +#!/bin/sh +set -e + +echo -n "TEST profile summary on CLI error... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +run_agentfs() { + if [ -n "${AGENTFS_BIN:-}" ]; then + "$AGENTFS_BIN" "$@" + else + cargo run --quiet --manifest-path "$CLI_DIR/Cargo.toml" -- "$@" + fi +} + +output_file="$(mktemp "${TMPDIR:-/tmp}/agentfs-profile-error.XXXXXX")" +trap 'rm -f "$output_file"' EXIT + +assert_one_summary_on_failure() { + description="$1" + shift + + : >"$output_file" + set +e + AGENTFS_PROFILE=1 "$@" >"$output_file" 2>&1 + status=$? + set -e + + if [ "$status" -eq 0 ]; then + echo "FAILED: $description should fail" + cat "$output_file" + exit 1 + fi + + summary_count="$(grep -c '"event":"agentfs_profile_summary"' "$output_file" || true)" + if [ "$summary_count" -ne 1 ]; then + echo "FAILED: expected exactly one profile summary for $description, saw $summary_count" + cat "$output_file" + exit 1 + fi +} + +assert_one_summary_on_failure \ + "invalid clap arguments" \ + run_agentfs --definitely-not-an-agentfs-option + +assert_one_summary_on_failure \ + "invalid encryption options" \ + run_agentfs fs --key deadbeef /tmp/agentfs-profile-error.db ls / + +set +e +AGENTFS_PROFILE=1 run_agentfs completions show >"$output_file" 2>&1 +status=$? +set -e + +if [ "$status" -ne 0 ]; then + echo "FAILED: completions show should succeed" + cat "$output_file" + exit 1 +fi + +summary_count="$(grep -c '"event":"agentfs_profile_summary"' "$output_file" || true)" +if [ "$summary_count" -ne 1 ]; then + echo "FAILED: expected exactly one profile summary on success, saw $summary_count" + cat "$output_file" + exit 1 +fi + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-run-bash.sh b/crates/agentfs-cli/tests/test-run-bash.sh new file mode 100755 index 00000000..c9b2af87 --- /dev/null +++ b/crates/agentfs-cli/tests/test-run-bash.sh @@ -0,0 +1,50 @@ +#!/bin/sh +set -eu + +echo -n "TEST interactive bash session... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-run-bash.XXXXXX")" +SESSION_ID="run-bash-$$" + +cleanup() { + rm -rf "$ROOT" "${HOME:?}/.agentfs/run/${SESSION_ID}" +} +trap cleanup EXIT INT TERM + +run_agentfs() { + if [ -n "${AGENTFS_BIN:-}" ]; then + "$AGENTFS_BIN" "$@" + else + cargo run --quiet --manifest-path "$CLI_DIR/Cargo.toml" -- "$@" + fi +} + +# The temp root is the overlay base layer. The session delta DB lands under +# ~/.agentfs/run/; pass an explicit session id so cleanup can remove +# exactly the session dir this test created (never sweep ~/.agentfs/run). +cd "$ROOT" + +# Run bash session in overlay: write a file and read it back +# The current directory becomes copy-on-write with the overlay sandbox +output=$(run_agentfs run --session "$SESSION_ID" /bin/bash -c ' +echo "hello from agent" > hello.txt +cat hello.txt +' 2>&1) + +# Verify we got the expected output +echo "$output" | grep -q "hello from agent" || { + echo "FAILED" + echo "$output" + exit 1 +} + +# Verify the file was NOT written to the host (it's in the delta layer) +if [ -f "hello.txt" ]; then + echo "FAILED: hello.txt should not exist on host filesystem" + exit 1 +fi + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-run-git.sh b/crates/agentfs-cli/tests/test-run-git.sh new file mode 100755 index 00000000..fd945c7d --- /dev/null +++ b/crates/agentfs-cli/tests/test-run-git.sh @@ -0,0 +1,72 @@ +#!/bin/sh +set -eu + +echo -n "TEST git init and commit in overlay... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-run-git.XXXXXX")" +SESSION_ID="run-git-$$" + +cleanup() { + rm -rf "$ROOT" "${HOME:?}/.agentfs/run/${SESSION_ID}" +} +trap cleanup EXIT INT TERM + +run_agentfs() { + if [ -n "${AGENTFS_BIN:-}" ]; then + "$AGENTFS_BIN" "$@" + else + cargo run --quiet --manifest-path "$CLI_DIR/Cargo.toml" -- "$@" + fi +} + +# The user PATH may route `git` through a hook-manager shim that daemonizes +# out of test repos (library/environment.md); pin the distro binary. +mkdir -p "$ROOT/bin" +for candidate in /usr/bin/git /bin/git; do + if [ -x "$candidate" ]; then + ln -sf "$candidate" "$ROOT/bin/git" + break + fi +done +[ -e "$ROOT/bin/git" ] || ln -sf "$(command -v git)" "$ROOT/bin/git" +PATH="$ROOT/bin:$PATH" +export PATH + +# The temp root is the overlay base layer. The session delta DB lands under +# ~/.agentfs/run/; pass an explicit session id so cleanup can remove +# exactly the session dir this test created (never sweep ~/.agentfs/run). +cd "$ROOT" + +# Run git operations in overlay: init, add, commit. +# Identity is set explicitly: read scoping hides ~/.gitconfig inside the +# sandbox, and CI runners have no global identity anyway. +output=$(run_agentfs run --session "$SESSION_ID" /bin/bash -c ' +export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null +export GIT_AUTHOR_NAME="AgentFS Test" GIT_AUTHOR_EMAIL="agentfs-test@example.com" +export GIT_COMMITTER_NAME="AgentFS Test" GIT_COMMITTER_EMAIL="agentfs-test@example.com" +mkdir test-git-repo +cd test-git-repo +git init +echo "hello" > hello.txt +git add hello.txt +git commit -m "Initial commit" +git log --oneline +' 2>&1) + +# Verify we got a successful commit (git log shows commit hash and message) +echo "$output" | grep -q "Initial commit" || { + echo "FAILED" + echo "$output" + exit 1 +} + +# Verify the directory was NOT written to the host (it's in the delta layer) +if [ -d "test-git-repo" ]; then + echo "FAILED: test-git-repo should not exist on host filesystem" + exit 1 +fi + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-run-read-scoping.sh b/crates/agentfs-cli/tests/test-run-read-scoping.sh new file mode 100755 index 00000000..221a8cb8 --- /dev/null +++ b/crates/agentfs-cli/tests/test-run-read-scoping.sh @@ -0,0 +1,84 @@ +#!/bin/sh +set -u + +# Read scoping (sacred invariant 2 / VAL-CROSS-015): inside `agentfs run +# --no-default-allows` a command must not be able to read host files placed +# outside the allowed set (home and temp dirs, including /dev/shm, are +# hidden), while re-running with `--allow ` re-exposes that directory; +# writes to scoped host paths never reach the host. + +echo "TEST run read scoping..." + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +run_agentfs() { + if [ -n "${AGENTFS_BIN:-}" ]; then + "$AGENTFS_BIN" "$@" + else + cargo run --quiet --manifest-path "$CLI_DIR/Cargo.toml" -- "$@" + fi +} + +session_prefix="scope-test-$$" +tmp_secret_dir="$(mktemp -d)" +home_secret_dir="$(mktemp -d "${HOME}/.agentfs-scope-test.XXXXXX")" +shm_secret_dir="$(mktemp -d /dev/shm/agentfs-scope-test.XXXXXX)" + +cleanup() { + rm -rf "$tmp_secret_dir" "$home_secret_dir" "$shm_secret_dir" + rm -rf "${HOME:?}/.agentfs/run/${session_prefix}-denied" \ + "${HOME:?}/.agentfs/run/${session_prefix}-write" \ + "${HOME:?}/.agentfs/run/${session_prefix}-allowed" +} +trap cleanup EXIT + +echo "s3cr3t-tmp" >"$tmp_secret_dir/secret.txt" +echo "s3cr3t-home" >"$home_secret_dir/secret.txt" +echo "s3cr3t-shm" >"$shm_secret_dir/secret.txt" + +fail() { + echo "FAILED: $1" + shift + for extra in "$@"; do + echo "$extra" + done + exit 1 +} + +# Leg 1 (denied reads): none of the tmp, home, or /dev/shm secrets is reachable. +denied_output=$(run_agentfs run --no-default-allows --session "${session_prefix}-denied" \ + /bin/sh -c "cat '$tmp_secret_dir/secret.txt'; cat '$shm_secret_dir/secret.txt'; cat '$home_secret_dir/secret.txt'" 2>&1) +denied_status=$? +case "$denied_output" in +*s3cr3t*) fail "secret bytes leaked into the denied sandbox" "$denied_output" ;; +esac +[ "$denied_status" -ne 0 ] || fail "denied run exited 0; expected the final cat to fail" "$denied_output" + +# Leg 2 (denied writes): writes to scoped host paths (incl. /dev/shm, which the +# ro-remount pass skips under its /dev prefix) must not reach the host. +write_output=$(run_agentfs run --no-default-allows --session "${session_prefix}-write" \ + /bin/sh -c "echo intruder >'$tmp_secret_dir/evil.txt'; echo intruder >'$shm_secret_dir/evil.txt'; echo intruder >/dev/shm/agentfs-scope-test-stray-$$.txt" 2>&1) +[ ! -f "$tmp_secret_dir/evil.txt" ] || fail "sandbox write escaped to the host" "$write_output" +[ ! -f "$shm_secret_dir/evil.txt" ] || fail "sandbox write escaped to host /dev/shm" "$write_output" +[ ! -f "/dev/shm/agentfs-scope-test-stray-$$.txt" ] || fail "sandbox write escaped to host /dev/shm root" "$write_output" +[ "$(cat "$tmp_secret_dir/secret.txt")" = "s3cr3t-tmp" ] || fail "host secret was mutated" "$write_output" +[ "$(cat "$shm_secret_dir/secret.txt")" = "s3cr3t-shm" ] || fail "host shm secret was mutated" "$write_output" + +# Leg 3 (allowed reads): --allow re-exposes the directory with exact bytes. +allowed_output=$(run_agentfs run --no-default-allows --allow "$tmp_secret_dir" \ + --session "${session_prefix}-allowed" \ + /bin/sh -c "cat '$tmp_secret_dir/secret.txt'" 2>&1) +allowed_status=$? +[ "$allowed_status" -eq 0 ] || fail "allowed run failed" "$allowed_output" +case "$allowed_output" in +*s3cr3t-tmp*) : ;; +*) fail "allowed read did not return the secret bytes" "$allowed_output" ;; +esac + +# Host census: the secret dirs hold exactly the files this test created. +[ "$(ls -A "$tmp_secret_dir")" = "secret.txt" ] || fail "unexpected host mutation in $tmp_secret_dir" +[ "$(ls -A "$home_secret_dir")" = "secret.txt" ] || fail "unexpected host mutation in $home_secret_dir" +[ "$(ls -A "$shm_secret_dir")" = "secret.txt" ] || fail "unexpected host mutation in $shm_secret_dir" + +echo "OK" diff --git a/cli/tests/test-run-syscalls.sh b/crates/agentfs-cli/tests/test-run-syscalls.sh similarity index 66% rename from cli/tests/test-run-syscalls.sh rename to crates/agentfs-cli/tests/test-run-syscalls.sh index 7c0a9622..fd6b8906 100755 --- a/cli/tests/test-run-syscalls.sh +++ b/crates/agentfs-cli/tests/test-run-syscalls.sh @@ -3,27 +3,51 @@ # Test syscalls through agentfs run (FUSE overlay). # # This tests the copy-on-write behavior when files exist in the base layer -# (host filesystem) and are modified through the overlay. +# and are modified through the overlay. # # Requires user namespaces support. # -set -e +set -eu echo -n "TEST syscalls (agentfs run - FUSE overlay)... " -DIR="$(dirname "$0")" +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" -# Compile the test program +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-run-syscalls.XXXXXX")" +SESSION_ID="run-syscalls-$$" + +cleanup() { + rm -rf "$ROOT" "${HOME:?}/.agentfs/run/${SESSION_ID}" +} +trap cleanup EXIT INT TERM + +fail() { + echo "FAILED: $*" + exit 1 +} + +run_agentfs() { + if [ -n "${AGENTFS_BIN:-}" ]; then + "$AGENTFS_BIN" "$@" + else + cargo run --quiet --manifest-path "$CLI_DIR/Cargo.toml" -- "$@" + fi +} + +# Compile the test program (artifacts stay in the source tree; gitignored). make -C "$DIR/syscall" clean > /dev/null 2>&1 make -C "$DIR/syscall" > /dev/null 2>&1 +cp "$DIR/syscall/test-syscalls" "$ROOT/test-syscalls" -TEST_DB="agent.db" - -# Clean up any existing test database -rm -f "$TEST_DB" "${TEST_DB}-wal" "${TEST_DB}-shm" +# The temp root is the overlay base layer; `init` puts its DB under +# $ROOT/.agentfs instead of the repo working tree. The run session's delta DB +# lands under ~/.agentfs/run/; pass an explicit session id so cleanup +# can remove exactly the session dir this test created (never sweep +# ~/.agentfs/run). +cd "$ROOT" -# Initialize the database -cargo run -- init > /dev/null 2>&1 +run_agentfs init > /dev/null 2>&1 # Create pre-existing files in the BASE LAYER (current directory) # These will trigger copy-on-write when modified through the overlay @@ -64,23 +88,15 @@ echo -n "content for fallocate copyup test" > copyup_fallocate_test.txt # - Files from current directory are visible (base layer) # - Modifications go to the delta layer (AgentFS database) # - O_APPEND on existing.txt triggers copy-on-write -if ! output=$(cargo run -- run "$DIR/syscall/test-syscalls" . 2>&1); then - echo "FAILED" - echo "Output was: $output" - rm -rf "$TEST_DB" "${TEST_DB}-wal" "${TEST_DB}-shm" existing.txt test.txt subdir executable_base.txt copyup_*_test.txt readonly.txt - exit 1 -fi +output=$(run_agentfs run --session "$SESSION_ID" ./test-syscalls . 2>&1) || + fail "run exited nonzero +Output was: $output" -echo "$output" | grep -q "All tests passed!" || { - echo "FAILED: 'All tests passed!' not found" - echo "Output was: $output" - rm -rf "$TEST_DB" "${TEST_DB}-wal" "${TEST_DB}-shm" existing.txt test.txt subdir executable_base.txt copyup_*_test.txt readonly.txt - exit 1 -} +echo "$output" | grep -q "All tests passed!" || + fail "'All tests passed!' not found +Output was: $output" # Note: output.txt is created in the delta layer (session-specific) so we can't # verify it with a separate agentfs run. The "All tests passed!" check is sufficient. -rm -rf "$TEST_DB" "${TEST_DB}-wal" "${TEST_DB}-shm" existing.txt test.txt subdir executable_base.txt copyup_*_test.txt readonly.txt - echo "OK" diff --git a/crates/agentfs-cli/tests/test-second-opener.sh b/crates/agentfs-cli/tests/test-second-opener.sh new file mode 100755 index 00000000..f79f4619 --- /dev/null +++ b/crates/agentfs-cli/tests/test-second-opener.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env sh +# +# Second-opener-of-one-DB regression (m7-cli-polish, VAL-CLI-025): +# while one `agentfs exec` holds a database open through a mount, a second +# opener of the same database must fail cleanly — exit nonzero with a single +# concise `Error:` line (turso file lock), bounded by a timeout, without +# panicking, and without disturbing the first session. Afterwards integrity +# on the database must be clean. +set -eu + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +echo -n "TEST second opener of one DB... " + +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-second-opener.XXXXXX")" +FIRST_PID="" + +cleanup() { + if [ -n "$FIRST_PID" ]; then + kill "$FIRST_PID" 2>/dev/null || true + wait "$FIRST_PID" 2>/dev/null || true + fi + rm -rf "$ROOT" +} +trap cleanup EXIT INT TERM + +fail() { + echo "FAILED: $*" + exit 1 +} + +# Resolve the binary once: background legs must track the agentfs PID itself +# (backgrounding a wrapper orphans the real process on cleanup kill). +if [ -n "${AGENTFS_BIN:-}" ]; then + BIN="$AGENTFS_BIN" +else + cargo +nightly build --quiet --manifest-path "$CLI_DIR/Cargo.toml" || { + echo "FAILED: could not build agentfs" + exit 1 + } + BIN="$CLI_DIR/../../target/debug/agentfs" +fi +if [ ! -x "$BIN" ]; then + echo "FAILED: agentfs binary not found at $BIN" + exit 1 +fi + +run_agentfs() { + "$BIN" "$@" +} + +# agentfs_dir() is cwd-relative, so init from inside $ROOT. +(cd "$ROOT" && run_agentfs init shared) >"$ROOT/init.log" 2>&1 || + fail "init failed: $(cat "$ROOT/init.log")" +DB="$ROOT/.agentfs/shared.db" +[ -f "$DB" ] || fail "init did not create $DB" +run_agentfs fs "$DB" write /keep.txt kept >"$ROOT/write.log" 2>&1 || + fail "fs write failed: $(cat "$ROOT/write.log")" + +# First opener: exec holds the DB (and its file lock) while the child sleeps. +"$BIN" exec "$DB" sh -c 'echo first-ready && sleep 8' \ + >"$ROOT/first.log" 2>&1 & +FIRST_PID=$! + +WAITED=0 +while [ "$WAITED" -lt 40 ]; do + if grep -q 'first-ready' "$ROOT/first.log" 2>/dev/null; then + break + fi + kill -0 "$FIRST_PID" 2>/dev/null || fail "first exec exited early: $(cat "$ROOT/first.log")" + sleep 0.25 + WAITED=$((WAITED + 1)) +done +grep -q 'first-ready' "$ROOT/first.log" || fail "first exec never became ready" + +# Second opener must fail cleanly and promptly: nonzero exit, exactly one +# Error: line, no panic/debug formatting, bounded (no hang). +"$BIN" exec "$DB" sh -c true >"$ROOT/second.log" 2>&1 & +SECOND_PID=$! +WAITED=0 +while kill -0 "$SECOND_PID" 2>/dev/null; do + if [ "$WAITED" -ge 120 ]; then + kill "$SECOND_PID" 2>/dev/null || true + fail "second opener hung past 30s: $(cat "$ROOT/second.log")" + fi + sleep 0.25 + WAITED=$((WAITED + 1)) +done +set +e +wait "$SECOND_PID" +SECOND_STATUS=$? +set -e + +[ "$SECOND_STATUS" -ne 0 ] || fail "second opener unexpectedly succeeded: $(cat "$ROOT/second.log")" + +ERROR_LINES=$(grep -c '^Error: ' "$ROOT/second.log" || true) +[ "$ERROR_LINES" -eq 1 ] || fail "expected exactly one Error: line, got $ERROR_LINES: $(cat "$ROOT/second.log")" +if grep -Eq 'panicked|Error \{|backtrace' "$ROOT/second.log"; then + fail "second opener leaked panic/debug formatting: $(cat "$ROOT/second.log")" +fi + +# The first session must finish normally and the DB must stay healthy. +wait "$FIRST_PID" +FIRST_STATUS=$? +FIRST_PID="" +[ "$FIRST_STATUS" -eq 0 ] || fail "first exec failed after second-opener attempt: $(cat "$ROOT/first.log")" + +run_agentfs fs "$DB" cat /keep.txt >"$ROOT/cat.log" 2>&1 || + fail "post-test cat failed: $(cat "$ROOT/cat.log")" +grep -q 'kept' "$ROOT/cat.log" || fail "post-test content mismatch: $(cat "$ROOT/cat.log")" + +run_agentfs integrity --json "$DB" >"$ROOT/integrity.json" 2>&1 || + fail "integrity failed: $(cat "$ROOT/integrity.json")" + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-sigkill-recovery.sh b/crates/agentfs-cli/tests/test-sigkill-recovery.sh new file mode 100755 index 00000000..2c69ef55 --- /dev/null +++ b/crates/agentfs-cli/tests/test-sigkill-recovery.sh @@ -0,0 +1,339 @@ +#!/bin/sh +# +# SIGKILL recovery regression for mount-owning run sessions. +# +# Killing the owner cannot run normal teardown, so this pins the recovery +# contract: the supervised workload dies with the owner, the stale mount is +# externally cleanable, fsynced data is still present after remount, and the DB +# integrity check stays clean. +# +set -eu + +echo -n "TEST SIGKILL recovery for run owner... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" +REPO_ROOT="$(cd "$CLI_DIR/../.." && pwd)" +CARGO_MANIFEST="$CLI_DIR/Cargo.toml" +AGENTFS_BIN="${AGENTFS_BIN:-}" +START_TIMEOUT="${SIGKILL_RECOVERY_START_TIMEOUT:-30}" +EXIT_TIMEOUT="${SIGKILL_RECOVERY_EXIT_TIMEOUT:-10}" +HOST_HOME="${HOME:-}" +CARGO_HOME_FOR_TEST="${CARGO_HOME:-$HOST_HOME/.cargo}" +RUSTUP_HOME_FOR_TEST="${RUSTUP_HOME:-$HOST_HOME/.rustup}" +RUSTUP_TOOLCHAIN_FOR_TEST="${RUSTUP_TOOLCHAIN:-nightly}" + +TEST_ROOT="" +TEST_HOME="" +WORKDIR="" +LOGDIR="" +SESSION_ID="" +RUN_DIR="" +DELTA_DB="" +FUSE_MNT="" +ACK_LOG="" +READY_FILE="" +CHILD_PID_FILE="" +OWNER_PID="" +OWNER_LOG="" +REMOUNT_PID="" +REMOUNT="" +REMOUNT_LOG="" + +skip() { + echo "SKIP: $*" + exit 0 +} + +fail() { + echo "FAILED: $*" + exit 1 +} + +case "$(uname -s)" in + Linux) + ;; + *) + skip "requires Linux namespaces and FUSE" + ;; +esac + +command -v cargo >/dev/null 2>&1 || skip "cargo is unavailable" +command -v mountpoint >/dev/null 2>&1 || skip "mountpoint is unavailable" +command -v sha256sum >/dev/null 2>&1 || skip "sha256sum is unavailable" +command -v timeout >/dev/null 2>&1 || skip "timeout is unavailable" +[ -x /bin/bash ] || skip "/bin/bash is unavailable" +[ -e /dev/fuse ] || skip "requires /dev/fuse for FUSE mounts" + +if [ -r /proc/sys/kernel/unprivileged_userns_clone ] && + [ "$(cat /proc/sys/kernel/unprivileged_userns_clone)" = "0" ]; then + skip "unprivileged user namespaces are disabled" +fi + +if [ -z "$AGENTFS_BIN" ]; then + cargo build --quiet --manifest-path "$CARGO_MANIFEST" >/dev/null 2>&1 || + fail "failed to build agentfs CLI" + AGENTFS_BIN="$REPO_ROOT/target/debug/agentfs" +fi + +[ -x "$AGENTFS_BIN" ] || fail "AGENTFS_BIN is not executable: $AGENTFS_BIN" + +unmount_path() { + path="$1" + if [ -z "$path" ]; then + return + fi + if ! mountpoint -q "$path" 2>/dev/null; then + return + fi + if command -v fusermount3 >/dev/null 2>&1; then + fusermount3 -u "$path" 2>/dev/null || fusermount3 -uz "$path" 2>/dev/null || true + elif command -v fusermount >/dev/null 2>&1; then + fusermount -u "$path" 2>/dev/null || true + else + umount "$path" 2>/dev/null || umount -l "$path" 2>/dev/null || true + fi +} + +wait_pid_exit() { + pid="$1" + seconds="$2" + deadline=$(( $(date +%s) + seconds )) + while kill -0 "$pid" 2>/dev/null; do + if [ "$(date +%s)" -ge "$deadline" ]; then + return 1 + fi + sleep 0.1 + done + return 0 +} + +cleanup() { + set +e + if [ -n "$REMOUNT_PID" ] && kill -0 "$REMOUNT_PID" 2>/dev/null; then + kill -TERM "$REMOUNT_PID" 2>/dev/null || true + wait_pid_exit "$REMOUNT_PID" 5 || kill -KILL "$REMOUNT_PID" 2>/dev/null || true + wait "$REMOUNT_PID" 2>/dev/null || true + fi + REMOUNT_PID="" + + if [ -n "$OWNER_PID" ] && kill -0 "$OWNER_PID" 2>/dev/null; then + kill -TERM "$OWNER_PID" 2>/dev/null || true + wait_pid_exit "$OWNER_PID" 5 || kill -KILL "$OWNER_PID" 2>/dev/null || true + wait "$OWNER_PID" 2>/dev/null || true + fi + OWNER_PID="" + + if [ -n "$CHILD_PID_FILE" ] && [ -f "$CHILD_PID_FILE" ]; then + child_pid="$(cat "$CHILD_PID_FILE" 2>/dev/null)" + case "$child_pid" in + ''|*[!0-9]*) + ;; + *) + if kill -0 "$child_pid" 2>/dev/null; then + kill -KILL "$child_pid" 2>/dev/null || true + wait_pid_exit "$child_pid" 5 || true + fi + ;; + esac + fi + + unmount_path "$REMOUNT" + unmount_path "$FUSE_MNT" + + if [ -n "$TEST_ROOT" ] && [ -d "$TEST_ROOT" ]; then + case "$TEST_ROOT" in + "${TMPDIR:-/tmp}"/agentfs-sigkill-recovery.*) + rm -rf "$TEST_ROOT" + ;; + *) + echo "WARNING: refusing to remove unexpected temp root: $TEST_ROOT" + ;; + esac + fi + set -e +} + +trap cleanup EXIT + +wait_for_owner_ready() { + deadline=$(( $(date +%s) + START_TIMEOUT )) + while [ "$(date +%s)" -le "$deadline" ]; do + if [ -s "$ACK_LOG" ] && [ -f "$READY_FILE" ] && [ -f "$CHILD_PID_FILE" ] && + [ -f "$DELTA_DB" ] && mountpoint -q "$FUSE_MNT" 2>/dev/null; then + return 0 + fi + if ! kill -0 "$OWNER_PID" 2>/dev/null; then + wait "$OWNER_PID" 2>/dev/null || true + return 1 + fi + sleep 0.1 + done + return 1 +} + +dump_context() { + label="$1" + echo + echo "FAILED: $label" + if [ -n "$OWNER_LOG" ] && [ -f "$OWNER_LOG" ]; then + echo "owner log:" + sed 's/^/ /' "$OWNER_LOG" | tail -n 120 + fi + if [ -n "$REMOUNT_LOG" ] && [ -f "$REMOUNT_LOG" ]; then + echo "remount log:" + sed 's/^/ /' "$REMOUNT_LOG" | tail -n 80 + fi + if [ -n "$FUSE_MNT" ]; then + echo "mount table:" + mount | grep "$FUSE_MNT" || true + fi +} + +wait_for_remount() { + deadline=$(( $(date +%s) + START_TIMEOUT )) + while [ "$(date +%s)" -le "$deadline" ]; do + if mountpoint -q "$REMOUNT" 2>/dev/null; then + return 0 + fi + if ! kill -0 "$REMOUNT_PID" 2>/dev/null; then + wait "$REMOUNT_PID" 2>/dev/null || true + return 1 + fi + sleep 0.1 + done + return 1 +} + +assert_acked_files_present() { + while read -r rel_path expected_hash; do + [ -n "$rel_path" ] || continue + if [ ! -f "$REMOUNT/$rel_path" ]; then + dump_context "acked file missing after remount: $rel_path" + exit 1 + fi + observed_hash="$(sha256sum "$REMOUNT/$rel_path" | awk '{print $1}')" + if [ "$observed_hash" != "$expected_hash" ]; then + dump_context "acked file hash mismatch for $rel_path: expected $expected_hash observed $observed_hash" + exit 1 + fi + done <"$ACK_LOG" +} + +TEST_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-sigkill-recovery.XXXXXX")" +TEST_HOME="$TEST_ROOT/home" +WORKDIR="$TEST_ROOT/work" +LOGDIR="$TEST_ROOT/logs" +SESSION_ID="sigkill-recovery-$$" +RUN_DIR="$TEST_HOME/.agentfs/run/$SESSION_ID" +DELTA_DB="$RUN_DIR/delta.db" +FUSE_MNT="$RUN_DIR/mnt" +ACK_LOG="$LOGDIR/acked.txt" +READY_FILE="$LOGDIR/ready" +CHILD_PID_FILE="$LOGDIR/child.pid" +OWNER_LOG="$LOGDIR/owner.log" +REMOUNT="$TEST_ROOT/remount" +REMOUNT_LOG="$LOGDIR/remount.log" +mkdir -p "$TEST_HOME/.cache" "$TEST_HOME/.config" "$WORKDIR" "$LOGDIR" "$REMOUNT" +: >"$ACK_LOG" + +( + cd "$WORKDIR" + HOME="$TEST_HOME" \ + XDG_CACHE_HOME="$TEST_HOME/.cache" \ + XDG_CONFIG_HOME="$TEST_HOME/.config" \ + CARGO_HOME="$CARGO_HOME_FOR_TEST" \ + RUSTUP_HOME="$RUSTUP_HOME_FOR_TEST" \ + RUSTUP_TOOLCHAIN="$RUSTUP_TOOLCHAIN_FOR_TEST" \ + AGENTFS_FUSE_URING=0 \ + "$AGENTFS_BIN" run --session "$SESSION_ID" --allow "$LOGDIR" \ + /bin/bash -c ' +set -euo pipefail +printf "%s\n" "$$" > "$1" +mkdir -p sigkill +for i in 1 2 3 4 5; do + rel="sigkill/acked-$i.txt" + printf "acked-payload-%s\n" "$i" > "$rel" + python3 - "$rel" <<'"'"'PY'"'"' +import os +import pathlib +import sys + +path = pathlib.Path(sys.argv[1]) +fd = os.open(path, os.O_RDWR) +try: + os.fsync(fd) +finally: + os.close(fd) +dir_fd = os.open(path.parent, os.O_RDONLY) +try: + os.fsync(dir_fd) +finally: + os.close(dir_fd) +PY + hash="$(sha256sum "$rel" | awk "{print \$1}")" + printf "%s %s\n" "$rel" "$hash" >> "$2" +done +: > "$3" +while :; do + sleep 1 +done +' agentfs-sigkill-workload "$CHILD_PID_FILE" "$ACK_LOG" "$READY_FILE" +) >"$OWNER_LOG" 2>&1 & +OWNER_PID=$! + +if ! wait_for_owner_ready; then + dump_context "owner did not reach fsynced write workload" + exit 1 +fi + +CHILD_PID="$(cat "$CHILD_PID_FILE")" +kill -KILL "$OWNER_PID" +wait "$OWNER_PID" 2>/dev/null || true +OWNER_PID="" + +if ! wait_pid_exit "$CHILD_PID" "$EXIT_TIMEOUT"; then + dump_context "workload child $CHILD_PID survived owner SIGKILL" + exit 1 +fi + +if ! mountpoint -q "$FUSE_MNT" 2>/dev/null; then + dump_context "expected stale FUSE mountpoint before external cleanup" + exit 1 +fi + +unmount_path "$FUSE_MNT" +if mountpoint -q "$FUSE_MNT" 2>/dev/null; then + dump_context "stale FUSE mountpoint could not be externally cleaned" + exit 1 +fi + +"$AGENTFS_BIN" integrity "$DELTA_DB" --json >/dev/null + +"$AGENTFS_BIN" mount "$DELTA_DB" "$REMOUNT" --backend fuse --foreground >"$REMOUNT_LOG" 2>&1 & +REMOUNT_PID=$! +if ! wait_for_remount; then + dump_context "remount did not become ready after SIGKILL recovery" + exit 1 +fi + +assert_acked_files_present + +kill -TERM "$REMOUNT_PID" 2>/dev/null || true +if ! wait_pid_exit "$REMOUNT_PID" "$EXIT_TIMEOUT"; then + dump_context "recovery remount did not exit after SIGTERM" + exit 1 +fi +wait "$REMOUNT_PID" 2>/dev/null || true +REMOUNT_PID="" + +if mount | grep "$SESSION_ID" >/dev/null 2>&1 || mount | grep "$REMOUNT" >/dev/null 2>&1; then + dump_context "mount residue after SIGKILL recovery" + exit 1 +fi +if ps -e -o pid= -o args= | grep "$SESSION_ID" | grep agentfs | grep -v grep >/dev/null 2>&1; then + dump_context "agentfs process residue after SIGKILL recovery" + exit 1 +fi + +echo "OK" diff --git a/crates/agentfs-cli/tests/test-signal-teardown.sh b/crates/agentfs-cli/tests/test-signal-teardown.sh new file mode 100755 index 00000000..0429347c --- /dev/null +++ b/crates/agentfs-cli/tests/test-signal-teardown.sh @@ -0,0 +1,372 @@ +#!/bin/sh +# +# Signal teardown regressions for mount-owning CLI commands. +# +# These cases deliver real SIGINT/SIGTERM to commands with live FUSE mounts and +# assert the command-owned cleanup path leaves no mount table entries, +# command processes, or temp mount directories behind. +# +set -eu + +echo -n "TEST signal teardown for clone/init/exec/mount... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" +REPO_ROOT="$(cd "$CLI_DIR/../.." && pwd)" +CARGO_MANIFEST="$CLI_DIR/Cargo.toml" +AGENTFS_BIN="${AGENTFS_BIN:-}" +SIGNAL_DELAY="${SIGNAL_TEARDOWN_SIGNAL_DELAY:-3s}" +HOST_HOME="${HOME:-}" +CARGO_HOME_FOR_TEST="${CARGO_HOME:-$HOST_HOME/.cargo}" +RUSTUP_HOME_FOR_TEST="${RUSTUP_HOME:-$HOST_HOME/.rustup}" +RUSTUP_TOOLCHAIN_FOR_TEST="${RUSTUP_TOOLCHAIN:-nightly}" + +TEST_ROOT="" +TEST_HOME="" +BEFORE_INIT_DIRS="" +BEFORE_CLONE_DIRS="" +BEFORE_EXEC_DIRS="" +REAL_GIT="" + +skip() { + echo "SKIP: $*" + exit 0 +} + +case "$(uname -s)" in + Linux) + ;; + *) + skip "requires Linux FUSE" + ;; +esac + +command -v cargo >/dev/null 2>&1 || skip "cargo is unavailable" +command -v git >/dev/null 2>&1 || skip "git is unavailable" +command -v timeout >/dev/null 2>&1 || skip "timeout is unavailable" +[ -e /dev/fuse ] || skip "requires /dev/fuse for FUSE mounts" + +if [ -z "$AGENTFS_BIN" ]; then + cargo build --quiet --manifest-path "$CARGO_MANIFEST" >/dev/null 2>&1 || + { + echo "FAILED: failed to build agentfs CLI" + exit 1 + } + AGENTFS_BIN="$REPO_ROOT/target/debug/agentfs" +fi + +if [ ! -x "$AGENTFS_BIN" ]; then + echo "FAILED: AGENTFS_BIN is not executable: $AGENTFS_BIN" + exit 1 +fi + +snapshot_tmp_dirs() { + prefix="$1" + output="$2" + python3 - "$prefix" "$output" <<'PY' +import pathlib +import sys + +prefix, output = sys.argv[1:] +paths = sorted(str(path) for path in pathlib.Path("/tmp").glob(prefix + "*")) +pathlib.Path(output).write_text("\n".join(paths) + ("\n" if paths else "")) +PY +} + +assert_no_new_tmp_dirs() { + label="$1" + prefix="$2" + before="$3" + python3 - "$label" "$prefix" "$before" <<'PY' +import pathlib +import sys + +label, prefix, before_path = sys.argv[1:] +before = set(pathlib.Path(before_path).read_text().splitlines()) +after = {str(path) for path in pathlib.Path("/tmp").glob(prefix + "*")} +new = sorted(after - before) +if new: + print(f"FAILED: {label} left temp mount directories:") + for path in new: + print(f" {path}") + sys.exit(1) +PY +} + +cleanup_mounts_matching() { + pattern="$1" + mount | while IFS= read -r line; do + case "$line" in + *"$pattern"*) + mountpoint_path=$(printf '%s\n' "$line" | awk '{print $3}') + if [ -n "$mountpoint_path" ]; then + if command -v fusermount3 >/dev/null 2>&1; then + fusermount3 -u "$mountpoint_path" 2>/dev/null || fusermount3 -uz "$mountpoint_path" 2>/dev/null || true + elif command -v fusermount >/dev/null 2>&1; then + fusermount -u "$mountpoint_path" 2>/dev/null || true + else + umount "$mountpoint_path" 2>/dev/null || true + fi + fi + ;; + esac + done +} + +cleanup_new_tmp_dirs() { + prefix="$1" + before="$2" + if [ -z "$before" ] || [ ! -f "$before" ]; then + return + fi + python3 - "$prefix" "$before" <<'PY' | while IFS= read -r path; do +import pathlib +import sys + +prefix, before_path = sys.argv[1:] +before = set(pathlib.Path(before_path).read_text().splitlines()) +after = {str(path) for path in pathlib.Path("/tmp").glob(prefix + "*")} +for path in sorted(after - before): + print(path) +PY + case "$path" in + /tmp/agentfs-init-*|/tmp/agentfs-clone-*|/tmp/agentfs-exec-*) + if command -v fusermount3 >/dev/null 2>&1; then + fusermount3 -u "$path" 2>/dev/null || fusermount3 -uz "$path" 2>/dev/null || true + elif command -v fusermount >/dev/null 2>&1; then + fusermount -u "$path" 2>/dev/null || true + else + umount "$path" 2>/dev/null || true + fi + rm -rf "$path" 2>/dev/null || true + ;; + esac + done +} + +cleanup() { + set +e + if [ -n "$TEST_ROOT" ]; then + cleanup_mounts_matching "$TEST_ROOT" + cleanup_mounts_matching "signal-init-$$" + cleanup_mounts_matching "signal-exec-$$" + cleanup_mounts_matching "signal-mount-$$" + fi + if [ -n "$TEST_HOME" ]; then + cleanup_mounts_matching "$TEST_HOME" + fi + cleanup_new_tmp_dirs "agentfs-init-" "$BEFORE_INIT_DIRS" + cleanup_new_tmp_dirs "agentfs-clone-" "$BEFORE_CLONE_DIRS" + cleanup_new_tmp_dirs "agentfs-exec-" "$BEFORE_EXEC_DIRS" + if [ -n "$TEST_ROOT" ] && [ -d "$TEST_ROOT" ]; then + case "$TEST_ROOT" in + "${TMPDIR:-/tmp}"/agentfs-signal-teardown.*) + rm -rf "$TEST_ROOT" + ;; + *) + echo "WARNING: refusing to remove unexpected temp root: $TEST_ROOT" + ;; + esac + fi + set -e +} + +trap cleanup EXIT + +fail() { + echo "FAILED: $*" + exit 1 +} + +expected_status_for_signal() { + case "$1" in + INT) echo 130 ;; + TERM) echo 143 ;; + *) fail "unknown signal $1" ;; + esac +} + +run_timeout_expect() { + label="$1" + expected="$2" + shift 2 + set +e + "$@" + status=$? + set -e + if [ "$status" -ne "$expected" ]; then + echo + echo "FAILED: $label exited $status, expected $expected" + return 1 + fi +} + +assert_no_mount() { + label="$1" + pattern="$2" + if mount | grep "$pattern" >/dev/null 2>&1; then + echo + echo "FAILED: $label left a mount table entry for $pattern" + mount | grep "$pattern" || true + return 1 + fi +} + +assert_no_processes() { + label="$1" + pattern="$2" + if ps -e -o pid= -o args= | grep "$pattern" | grep -v grep >/dev/null 2>&1; then + echo + echo "FAILED: $label left process residue for $pattern" + ps -e -o pid= -o args= | grep "$pattern" | grep -v grep || true + return 1 + fi +} + +new_case_home() { + name="$1" + TEST_HOME="$TEST_ROOT/home-$name" + mkdir -p "$TEST_HOME/.cache" "$TEST_HOME/.config" "$TEST_HOME/git-hooks-none" + # Hookless git config: the user's hook manager must not daemonize out of + # case repos under this temp HOME (library/environment.md). + printf '[core]\n\thooksPath = %s\n' "$TEST_HOME/git-hooks-none" >"$TEST_HOME/.gitconfig" +} + +run_init_signal_case() { + signal="$1" + expected="$(expected_status_for_signal "$signal")" + new_case_home "init-$signal" + id="signal-init-$$-$signal" + + run_timeout_expect "init -c $signal" "$expected" \ + env HOME="$TEST_HOME" \ + XDG_CACHE_HOME="$TEST_HOME/.cache" \ + XDG_CONFIG_HOME="$TEST_HOME/.config" \ + CARGO_HOME="$CARGO_HOME_FOR_TEST" \ + RUSTUP_HOME="$RUSTUP_HOME_FOR_TEST" \ + RUSTUP_TOOLCHAIN="$RUSTUP_TOOLCHAIN_FOR_TEST" \ + AGENTFS_FUSE_URING=0 \ + timeout --preserve-status --foreground -s "$signal" "$SIGNAL_DELAY" \ + "$AGENTFS_BIN" init "$id" --backend fuse -c 'exec sleep 30' + + assert_no_mount "init -c $signal" "agentfs:$id" + assert_no_processes "init -c $signal" "$TEST_HOME" + assert_no_new_tmp_dirs "init -c $signal" "agentfs-init-" "$BEFORE_INIT_DIRS" +} + +write_git_wrapper() { + wrapper_dir="$TEST_ROOT/git-wrapper" + mkdir -p "$wrapper_dir" + cat >"$wrapper_dir/git" <<'EOF' +#!/bin/sh +if [ "$1" = "clone" ]; then + for last do :; done + mkdir -p "$last/.git/objects" + printf 'clone wrapper touched the mounted filesystem\n' > "$last/probe.txt" + exec sleep 30 +fi +exec "$REAL_GIT" "$@" +EOF + chmod +x "$wrapper_dir/git" + echo "$wrapper_dir" +} + +run_clone_signal_case() { + signal="$1" + expected="$(expected_status_for_signal "$signal")" + new_case_home "clone-$signal" + db="$TEST_ROOT/clone-$signal.db" + source="$TEST_ROOT/source-$signal" + mkdir -p "$source" + wrapper_dir="$(write_git_wrapper)" + + run_timeout_expect "clone $signal" "$expected" \ + env HOME="$TEST_HOME" \ + XDG_CACHE_HOME="$TEST_HOME/.cache" \ + XDG_CONFIG_HOME="$TEST_HOME/.config" \ + CARGO_HOME="$CARGO_HOME_FOR_TEST" \ + RUSTUP_HOME="$RUSTUP_HOME_FOR_TEST" \ + RUSTUP_TOOLCHAIN="$RUSTUP_TOOLCHAIN_FOR_TEST" \ + AGENTFS_FUSE_URING=0 \ + REAL_GIT="$REAL_GIT" \ + PATH="$wrapper_dir:$PATH" \ + timeout --preserve-status --foreground -s "$signal" "$SIGNAL_DELAY" \ + "$AGENTFS_BIN" clone "$db" "$source" repo --backend fuse + + assert_no_mount "clone $signal" "$db" + assert_no_processes "clone $signal" "$TEST_ROOT" + assert_no_new_tmp_dirs "clone $signal" "agentfs-clone-" "$BEFORE_CLONE_DIRS" +} + +run_exec_signal_case() { + signal="$1" + expected="$(expected_status_for_signal "$signal")" + new_case_home "exec-$signal" + id="signal-exec-$$-$signal" + + env HOME="$TEST_HOME" "$AGENTFS_BIN" init "$id" >/dev/null 2>&1 + run_timeout_expect "exec $signal" "$expected" \ + env HOME="$TEST_HOME" \ + XDG_CACHE_HOME="$TEST_HOME/.cache" \ + XDG_CONFIG_HOME="$TEST_HOME/.config" \ + CARGO_HOME="$CARGO_HOME_FOR_TEST" \ + RUSTUP_HOME="$RUSTUP_HOME_FOR_TEST" \ + RUSTUP_TOOLCHAIN="$RUSTUP_TOOLCHAIN_FOR_TEST" \ + AGENTFS_FUSE_URING=0 \ + timeout --preserve-status --foreground -s "$signal" "$SIGNAL_DELAY" \ + "$AGENTFS_BIN" exec "$id" --backend fuse sh -c 'exec sleep 30' + + assert_no_mount "exec $signal" "agentfs:.*$id" + assert_no_processes "exec $signal" "$TEST_HOME" + assert_no_new_tmp_dirs "exec $signal" "agentfs-exec-" "$BEFORE_EXEC_DIRS" +} + +run_mount_foreground_signal_case() { + signal="$1" + new_case_home "mount-$signal" + id="signal-mount-$$-$signal" + mountpoint="$TEST_ROOT/mount-$signal" + mkdir -p "$mountpoint" + + env HOME="$TEST_HOME" "$AGENTFS_BIN" init "$id" >/dev/null 2>&1 + run_timeout_expect "mount --foreground $signal" 0 \ + env HOME="$TEST_HOME" \ + XDG_CACHE_HOME="$TEST_HOME/.cache" \ + XDG_CONFIG_HOME="$TEST_HOME/.config" \ + CARGO_HOME="$CARGO_HOME_FOR_TEST" \ + RUSTUP_HOME="$RUSTUP_HOME_FOR_TEST" \ + RUSTUP_TOOLCHAIN="$RUSTUP_TOOLCHAIN_FOR_TEST" \ + AGENTFS_FUSE_URING=0 \ + timeout --preserve-status --foreground -s "$signal" "$SIGNAL_DELAY" \ + "$AGENTFS_BIN" mount "$id" "$mountpoint" --backend fuse --foreground + + assert_no_mount "mount --foreground $signal" "$mountpoint" + assert_no_processes "mount --foreground $signal" "$TEST_HOME" +} + +TEST_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-signal-teardown.XXXXXX")" +BEFORE_INIT_DIRS="$TEST_ROOT/before-init-dirs.txt" +BEFORE_CLONE_DIRS="$TEST_ROOT/before-clone-dirs.txt" +BEFORE_EXEC_DIRS="$TEST_ROOT/before-exec-dirs.txt" +# Pin the distro git binary: the user's PATH may route `git` through a +# hook-manager shim that daemonizes out of test repos (library/environment.md). +REAL_GIT="" +for candidate in /usr/bin/git /bin/git; do + if [ -x "$candidate" ]; then + REAL_GIT="$candidate" + break + fi +done +[ -n "$REAL_GIT" ] || REAL_GIT="$(command -v git)" +snapshot_tmp_dirs "agentfs-init-" "$BEFORE_INIT_DIRS" +snapshot_tmp_dirs "agentfs-clone-" "$BEFORE_CLONE_DIRS" +snapshot_tmp_dirs "agentfs-exec-" "$BEFORE_EXEC_DIRS" + +for signal in INT TERM; do + run_init_signal_case "$signal" + run_clone_signal_case "$signal" + run_exec_signal_case "$signal" + run_mount_foreground_signal_case "$signal" +done + +echo "OK" diff --git a/cli/tests/test-symlinks.sh b/crates/agentfs-cli/tests/test-symlinks.sh similarity index 59% rename from cli/tests/test-symlinks.sh rename to crates/agentfs-cli/tests/test-symlinks.sh index 5a6b807b..8b4b519e 100755 --- a/cli/tests/test-symlinks.sh +++ b/crates/agentfs-cli/tests/test-symlinks.sh @@ -1,23 +1,45 @@ #!/bin/sh -set -e +set -eu echo -n "TEST symlink handling... " +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" + +ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-symlinks.XXXXXX")" +SESSION_PREFIX="symlinks-$$" + +cleanup() { + rm -rf "$ROOT" + for leg in 1 2 3 4 5; do + rm -rf "${HOME:?}/.agentfs/run/${SESSION_PREFIX}-${leg}" + done +} +trap cleanup EXIT INT TERM + +run_agentfs() { + if [ -n "${AGENTFS_BIN:-}" ]; then + "$AGENTFS_BIN" "$@" + else + cargo run --quiet --manifest-path "$CLI_DIR/Cargo.toml" -- "$@" + fi +} + +# The temp root is the overlay base layer. Each leg's session delta DB lands +# under ~/.agentfs/run/; pass explicit session ids so cleanup can +# remove exactly the session dirs this test created (never sweep +# ~/.agentfs/run). +cd "$ROOT" + # Create test directory with symlinks on the host (these will be visible in the sandbox) -TEST_DIR=".agentfs/symlink-test-$$" -rm -rf "$TEST_DIR" +TEST_DIR="symlink-test" mkdir -p "$TEST_DIR/target_dir" echo "test content" > "$TEST_DIR/target_dir/file.txt" ln -s target_dir "$TEST_DIR/link_to_dir" ln -s target_dir/file.txt "$TEST_DIR/link_to_file" -cleanup() { - rm -rf "$TEST_DIR" -} -trap cleanup EXIT - # Test 1 & 2: Verify symlinks are reported correctly (not as directories) -output=$(cargo run -- run /bin/bash -c "ls -la $TEST_DIR/" 2>&1) +output=$(run_agentfs run --session "$SESSION_PREFIX-1" /bin/bash -c "ls -la $TEST_DIR/" 2>&1) # The output should contain 'lrwxrwxrwx' for symlinks (not 'drwxr-xr-x' for directory) if ! echo "$output" | grep -qE "^lrwx.* link_to_dir"; then @@ -34,7 +56,7 @@ fi # Test 3: Verify rm can remove symlink to directory (this was the original bug) # Previously this would fail with "Is a directory" because symlinks were misidentified -output=$(cargo run -- run /bin/bash -c "rm $TEST_DIR/link_to_dir && echo 'symlink removed successfully'" 2>&1) +output=$(run_agentfs run --session "$SESSION_PREFIX-2" /bin/bash -c "rm $TEST_DIR/link_to_dir && echo 'symlink removed successfully'" 2>&1) if ! echo "$output" | grep -q "symlink removed successfully"; then echo "FAILED: could not remove symlink to directory" @@ -50,7 +72,7 @@ if ! cat "$TEST_DIR/target_dir/file.txt" | grep -q "test content"; then fi # Test 5: Create a symlink inside the sandbox (tests FUSE symlink creation) -output=$(cargo run -- run /bin/bash -c "ln -s target_dir/file.txt $TEST_DIR/new_symlink && readlink $TEST_DIR/new_symlink" 2>&1) +output=$(run_agentfs run --session "$SESSION_PREFIX-3" /bin/bash -c "ln -s target_dir/file.txt $TEST_DIR/new_symlink && readlink $TEST_DIR/new_symlink" 2>&1) if ! echo "$output" | grep -q "target_dir/file.txt"; then echo "FAILED: could not create symlink in sandbox" @@ -59,7 +81,7 @@ if ! echo "$output" | grep -q "target_dir/file.txt"; then fi # Test 6: Create and follow symlink to read file content -output=$(cargo run -- run /bin/bash -c "ln -s target_dir $TEST_DIR/new_dir_link && cat $TEST_DIR/new_dir_link/file.txt" 2>&1) +output=$(run_agentfs run --session "$SESSION_PREFIX-4" /bin/bash -c "ln -s target_dir $TEST_DIR/new_dir_link && cat $TEST_DIR/new_dir_link/file.txt" 2>&1) if ! echo "$output" | grep -q "test content"; then echo "FAILED: could not read through newly created symlink" @@ -68,7 +90,7 @@ if ! echo "$output" | grep -q "test content"; then fi # Test 7: Verify symlinks created in sandbox are visible via ls -l -output=$(cargo run -- run /bin/bash -c "ln -s foo $TEST_DIR/test_link && ls -la $TEST_DIR/test_link" 2>&1) +output=$(run_agentfs run --session "$SESSION_PREFIX-5" /bin/bash -c "ln -s foo $TEST_DIR/test_link && ls -la $TEST_DIR/test_link" 2>&1) if ! echo "$output" | grep -qE "^lrwx.*test_link -> foo"; then echo "FAILED: newly created symlink not shown correctly in ls" diff --git a/crates/agentfs-cli/tests/test-teardown-bounded.sh b/crates/agentfs-cli/tests/test-teardown-bounded.sh new file mode 100755 index 00000000..1d890383 --- /dev/null +++ b/crates/agentfs-cli/tests/test-teardown-bounded.sh @@ -0,0 +1,515 @@ +#!/bin/sh +# +# Bounded teardown regression for `agentfs run --session`. +# +# The owner process must exit promptly after SIGTERM while a real FUSE session +# has had joiners and filesystem activity. This exercises both the legacy +# /dev/fuse reader path and the FUSE-over-io_uring path on capable kernels. +# +# A third leg covers the parent abort error path: when the sandbox child dies +# before the namespace handshake completes, the parent must tear down its live +# FUSE mount gracefully before exiting instead of leaving a dead mount table +# entry behind. +# +set -eu + +echo -n "TEST bounded FUSE teardown on SIGTERM... " + +DIR="$(cd "$(dirname "$0")" && pwd)" +CLI_DIR="$(cd "$DIR/.." && pwd)" +REPO_ROOT="$(cd "$CLI_DIR/../.." && pwd)" +CARGO_MANIFEST="$CLI_DIR/Cargo.toml" +AGENTFS_BIN="${AGENTFS_BIN:-}" +TEARDOWN_TIMEOUT="${TEARDOWN_TIMEOUT:-10}" +START_TIMEOUT="${TEARDOWN_START_TIMEOUT:-30}" +HOST_HOME="${HOME:-}" +CARGO_HOME_FOR_TEST="${CARGO_HOME:-$HOST_HOME/.cargo}" +RUSTUP_HOME_FOR_TEST="${RUSTUP_HOME:-$HOST_HOME/.rustup}" +RUSTUP_TOOLCHAIN_FOR_TEST="${RUSTUP_TOOLCHAIN:-nightly}" + +TEST_ROOT="" +TEST_HOME="" +WORKDIR="" +LOGDIR="" +SESSION_ID="" +RUN_DIR="" +DELTA_DB="" +FUSE_MNT="" +OWNER_PID="" +OWNER_LOG="" +JOINER_LOG="" + +skip() { + echo "SKIP: $*" + exit 0 +} + +validate_positive_integer() { + name="$1" + value="$2" + case "$value" in + ''|*[!0-9]*) + echo "FAILED: $name must be a positive integer, got '$value'" + exit 1 + ;; + esac + if [ "$value" -le 0 ]; then + echo "FAILED: $name must be a positive integer, got '$value'" + exit 1 + fi +} + +case "$(uname -s)" in + Linux) + ;; + *) + skip "requires Linux namespaces and FUSE" + ;; +esac + +command -v cargo >/dev/null 2>&1 || skip "cargo is unavailable" +command -v git >/dev/null 2>&1 || skip "git is unavailable" +command -v mountpoint >/dev/null 2>&1 || skip "mountpoint is unavailable" +[ -x /bin/bash ] || skip "/bin/bash is unavailable" +[ -e /dev/fuse ] || skip "requires /dev/fuse for FUSE mounts" + +if [ -r /proc/sys/kernel/unprivileged_userns_clone ] && + [ "$(cat /proc/sys/kernel/unprivileged_userns_clone)" = "0" ]; then + skip "unprivileged user namespaces are disabled" +fi + +validate_positive_integer TEARDOWN_TIMEOUT "$TEARDOWN_TIMEOUT" +validate_positive_integer TEARDOWN_START_TIMEOUT "$START_TIMEOUT" + +if [ -z "$AGENTFS_BIN" ]; then + cargo build --quiet --manifest-path "$CARGO_MANIFEST" >/dev/null 2>&1 || + { + echo "FAILED: failed to build agentfs CLI" + exit 1 + } + AGENTFS_BIN="$REPO_ROOT/target/debug/agentfs" +fi + +if [ ! -x "$AGENTFS_BIN" ]; then + echo "FAILED: AGENTFS_BIN is not executable: $AGENTFS_BIN" + exit 1 +fi + +unmount_if_needed() { + if [ -n "$FUSE_MNT" ] && command -v mountpoint >/dev/null 2>&1 && + mountpoint -q "$FUSE_MNT" 2>/dev/null; then + if command -v fusermount3 >/dev/null 2>&1; then + fusermount3 -u "$FUSE_MNT" 2>/dev/null || true + elif command -v fusermount >/dev/null 2>&1; then + fusermount -u "$FUSE_MNT" 2>/dev/null || true + elif command -v umount >/dev/null 2>&1; then + umount "$FUSE_MNT" 2>/dev/null || true + fi + fi +} + +cleanup_current() { + set +e + + if [ -n "$OWNER_PID" ] && kill -0 "$OWNER_PID" 2>/dev/null; then + kill "$OWNER_PID" 2>/dev/null || true + waited=0 + while kill -0 "$OWNER_PID" 2>/dev/null && [ "$waited" -lt 20 ]; do + sleep 0.1 + waited=$((waited + 1)) + done + if kill -0 "$OWNER_PID" 2>/dev/null; then + kill -KILL "$OWNER_PID" 2>/dev/null || true + fi + wait "$OWNER_PID" 2>/dev/null || true + fi + OWNER_PID="" + + unmount_if_needed + + if [ -n "$TEST_ROOT" ] && [ -d "$TEST_ROOT" ]; then + case "$TEST_ROOT" in + "${TMPDIR:-/tmp}"/agentfs-teardown-bounded.*) + rm -rf "$TEST_ROOT" + ;; + *) + echo "WARNING: refusing to remove unexpected temp root: $TEST_ROOT" + ;; + esac + fi + + TEST_ROOT="" + TEST_HOME="" + WORKDIR="" + LOGDIR="" + SESSION_ID="" + RUN_DIR="" + DELTA_DB="" + FUSE_MNT="" + OWNER_LOG="" + JOINER_LOG="" + set -e +} + +finish_with_signal() { + cleanup_current + exit 130 +} + +trap cleanup_current EXIT +trap finish_with_signal INT TERM + +dump_failure_context() { + label="$1" + echo + echo "FAILED: $label" + if [ -n "$OWNER_LOG" ] && [ -f "$OWNER_LOG" ]; then + echo "owner log:" + sed 's/^/ /' "$OWNER_LOG" | tail -n 120 + fi + if [ -n "$JOINER_LOG" ] && [ -f "$JOINER_LOG" ]; then + echo "joiner log:" + sed 's/^/ /' "$JOINER_LOG" | tail -n 120 + fi + if [ -n "$OWNER_PID" ] && kill -0 "$OWNER_PID" 2>/dev/null; then + echo "owner threads:" + ps -T -p "$OWNER_PID" -o pid,tid,comm,wchan:32 2>/dev/null || true + fi + if [ -n "$FUSE_MNT" ]; then + echo "mount table:" + mount | grep "$FUSE_MNT" || true + fi +} + +wait_for_owner_ready() { + deadline=$(( $(date +%s) + START_TIMEOUT )) + while [ "$(date +%s)" -le "$deadline" ]; do + if [ -f "$DELTA_DB" ] && [ -f "$RUN_DIR/base_path" ] && + mountpoint -q "$FUSE_MNT" 2>/dev/null; then + return 0 + fi + if ! kill -0 "$OWNER_PID" 2>/dev/null; then + wait "$OWNER_PID" 2>/dev/null || true + return 1 + fi + sleep 0.2 + done + return 1 +} + +run_joiner_workload() { + leg="$1" + ( + cd "$WORKDIR" + HOME="$TEST_HOME" \ + XDG_CACHE_HOME="$TEST_HOME/.cache" \ + XDG_CONFIG_HOME="$TEST_HOME/.config" \ + CARGO_HOME="$CARGO_HOME_FOR_TEST" \ + RUSTUP_HOME="$RUSTUP_HOME_FOR_TEST" \ + RUSTUP_TOOLCHAIN="$RUSTUP_TOOLCHAIN_FOR_TEST" \ + AGENTFS_FUSE_URING="$leg" \ + "$AGENTFS_BIN" run --session "$SESSION_ID" \ + /bin/bash -c ' +set -euo pipefail +mkdir -p bounded/repo/src +printf "bounded teardown payload\n" > bounded/payload.txt +cat bounded/payload.txt >/dev/null +cd bounded/repo +"${GIT_REAL:-git}" init -q +"${GIT_REAL:-git}" config user.email "bounded@example.invalid" +"${GIT_REAL:-git}" config user.name "Bounded Teardown" +printf "hello\n" > src/file.txt +"${GIT_REAL:-git}" add . +"${GIT_REAL:-git}" commit -q -m init +"${GIT_REAL:-git}" fsck --strict --no-progress +' + ) >"$JOINER_LOG" 2>&1 +} + +verify_session_after_teardown() { + leg="$1" + verify_log="$LOGDIR/verify-after-term.log" + integrity_log="$LOGDIR/integrity-after-term.json" + + HOME="$TEST_HOME" \ + XDG_CACHE_HOME="$TEST_HOME/.cache" \ + XDG_CONFIG_HOME="$TEST_HOME/.config" \ + "$AGENTFS_BIN" integrity "$DELTA_DB" --json >"$integrity_log" 2>&1 || + { + dump_failure_context "$leg integrity check failed after SIGTERM teardown" + sed 's/^/ /' "$integrity_log" + return 1 + } + + ( + cd "$WORKDIR" + HOME="$TEST_HOME" \ + XDG_CACHE_HOME="$TEST_HOME/.cache" \ + XDG_CONFIG_HOME="$TEST_HOME/.config" \ + CARGO_HOME="$CARGO_HOME_FOR_TEST" \ + RUSTUP_HOME="$RUSTUP_HOME_FOR_TEST" \ + RUSTUP_TOOLCHAIN="$RUSTUP_TOOLCHAIN_FOR_TEST" \ + AGENTFS_FUSE_URING="$leg" \ + "$AGENTFS_BIN" run --session "$SESSION_ID" \ + /bin/bash -c ' +set -euo pipefail +test "$(cat bounded/payload.txt)" = "bounded teardown payload" +"${GIT_REAL:-git}" -C bounded/repo fsck --strict --no-progress +test -z "$("${GIT_REAL:-git}" -C bounded/repo status --porcelain)" +' + ) >"$verify_log" 2>&1 || { + dump_failure_context "$leg remount verification failed after SIGTERM teardown" + sed 's/^/ /' "$verify_log" + return 1 + } +} + +# Must run in the main shell, not a command substitution: the owner is this +# shell's child, and a subshell poller pins `kill -0` on the unreaped zombie +# forever under dash, where the blocked parent reaps nothing until the +# substitution pipe closes. Reports the teardown latency via `elapsed_ms`. +wait_for_owner_exit() { + elapsed_ms="" + start_ms="$(date +%s%3N)" + deadline_ms=$((start_ms + TEARDOWN_TIMEOUT * 1000)) + while kill -0 "$OWNER_PID" 2>/dev/null; do + now_ms="$(date +%s%3N)" + if [ "$now_ms" -ge "$deadline_ms" ]; then + return 1 + fi + sleep 0.1 + done + wait "$OWNER_PID" 2>/dev/null || true + OWNER_PID="" + end_ms="$(date +%s%3N)" + elapsed_ms=$((end_ms - start_ms)) + return 0 +} + +assert_no_session_residue() { + if [ -n "$FUSE_MNT" ] && mount | grep "$FUSE_MNT" >/dev/null 2>&1; then + return 1 + fi + if ps -e -o pid= -o args= | grep "$SESSION_ID" | grep agentfs | grep -v grep >/dev/null 2>&1; then + return 1 + fi + return 0 +} + +run_leg() { + leg="$1" + label="legacy" + if [ "$leg" = "1" ]; then + label="uring" + fi + + TEST_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-teardown-bounded.XXXXXX")" + TEST_HOME="$TEST_ROOT/home" + WORKDIR="$TEST_ROOT/work" + LOGDIR="$TEST_ROOT/logs" + SESSION_ID="teardown-bounded-$$-$leg" + RUN_DIR="$TEST_HOME/.agentfs/run/$SESSION_ID" + DELTA_DB="$RUN_DIR/delta.db" + FUSE_MNT="$RUN_DIR/mnt" + OWNER_LOG="$LOGDIR/owner.log" + JOINER_LOG="$LOGDIR/joiner.log" + mkdir -p "$TEST_HOME/.cache" "$TEST_HOME/.config" "$WORKDIR" "$LOGDIR" + + # The user's PATH may route `git` through a hook-manager shim that + # daemonizes out of test repos (library/environment.md); pin the distro + # binary and give the temp HOME a hookless git config. + mkdir -p "$TEST_HOME/bin" "$TEST_HOME/git-hooks-none" + GIT_REAL="" + for candidate in /usr/bin/git /bin/git; do + if [ -x "$candidate" ]; then + GIT_REAL="$candidate" + break + fi + done + [ -n "$GIT_REAL" ] || GIT_REAL="$(command -v git)" + ln -sf "$GIT_REAL" "$TEST_HOME/bin/git" + printf '[core]\n\thooksPath = %s\n' "$TEST_HOME/git-hooks-none" >"$TEST_HOME/.gitconfig" + PATH="$TEST_HOME/bin:$PATH" + # The PATH shim only holds host-side: inside `agentfs run` temp dirs are + # hidden, so sandboxed workloads must call "$GIT_REAL" by absolute path. + export PATH GIT_REAL + + ( + cd "$WORKDIR" + HOME="$TEST_HOME" \ + XDG_CACHE_HOME="$TEST_HOME/.cache" \ + XDG_CONFIG_HOME="$TEST_HOME/.config" \ + CARGO_HOME="$CARGO_HOME_FOR_TEST" \ + RUSTUP_HOME="$RUSTUP_HOME_FOR_TEST" \ + RUSTUP_TOOLCHAIN="$RUSTUP_TOOLCHAIN_FOR_TEST" \ + AGENTFS_FUSE_URING="$leg" \ + RUST_LOG=agentfs=info \ + "$AGENTFS_BIN" run --session "$SESSION_ID" \ + /bin/bash -c 'trap "exit 0" TERM INT; while :; do sleep 1; done' + ) >"$OWNER_LOG" 2>&1 & + OWNER_PID=$! + + if ! wait_for_owner_ready; then + dump_failure_context "$label owner did not become ready" + cleanup_current + return 1 + fi + + if ! run_joiner_workload "$leg"; then + dump_failure_context "$label joiner workload failed" + cleanup_current + return 1 + fi + + if [ "$leg" = "1" ] && [ -r /sys/module/fuse/parameters/enable_uring ] && + [ "$(cat /sys/module/fuse/parameters/enable_uring)" = "Y" ] && + ! grep -q "advertising FUSE_OVER_IO_URING" "$OWNER_LOG"; then + dump_failure_context "uring leg did not advertise FUSE_OVER_IO_URING" + cleanup_current + return 1 + fi + + if [ "$leg" = "0" ] && grep -q "advertising FUSE_OVER_IO_URING" "$OWNER_LOG"; then + dump_failure_context "legacy leg unexpectedly used FUSE_OVER_IO_URING" + cleanup_current + return 1 + fi + + kill "$OWNER_PID" 2>/dev/null || true + if ! wait_for_owner_exit; then + dump_failure_context "$label owner did not exit within ${TEARDOWN_TIMEOUT}s after SIGTERM" + cleanup_current + return 1 + fi + + if ! assert_no_session_residue; then + dump_failure_context "$label session left mount or process residue after ${elapsed_ms}ms teardown" + cleanup_current + return 1 + fi + + if ! verify_session_after_teardown "$leg"; then + cleanup_current + return 1 + fi + + if ! assert_no_session_residue; then + dump_failure_context "$label verification remount left residue" + cleanup_current + return 1 + fi + + cleanup_current + printf " %s=%sms" "$label" "$elapsed_ms" + return 0 +} + +# Parent abort-path teardown: capping max_user_namespaces at 0 inside a nested +# user namespace makes the sandbox child's unshare fail deterministically +# AFTER the parent's FUSE mount is live, driving the parent through its abort +# error path. The parent must exit 1 with the mount gone from the namespace's +# mount table (pre-fix it exited via process::exit without dropping the +# MountHandle, stranding a dead mount entry). +run_abort_leg() { + if ! command -v unshare >/dev/null 2>&1 || ! unshare -Ur true 2>/dev/null; then + echo "SKIP: nested user namespaces unavailable for the abort-path leg" + return 0 + fi + + TEST_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-teardown-bounded.XXXXXX")" + TEST_HOME="$TEST_ROOT/home" + WORKDIR="$TEST_ROOT/work" + LOGDIR="$TEST_ROOT/logs" + SESSION_ID="abort-path-$$" + OWNER_LOG="$LOGDIR/abort-leg.log" + mkdir -p "$TEST_HOME" "$WORKDIR" "$LOGDIR" + + inner="$TEST_ROOT/abort-inner.sh" + cat >"$inner" <<'INNER' +#!/bin/sh +set -u +BIN="$1" +TEST_HOME="$2" +WORKDIR="$3" +SESSION_ID="$4" +if ! echo 0 >/proc/sys/user/max_user_namespaces 2>/dev/null; then + echo "ABORT-LEG-SETUP-SKIP" + exit 0 +fi +cd "$WORKDIR" || exit 90 +rc=0 +HOME="$TEST_HOME" "$BIN" run --session "$SESSION_ID" -- /bin/true || rc=$? +echo "ABORT-LEG-EXIT=$rc" +if grep -qF "$TEST_HOME" /proc/self/mountinfo; then + echo "ABORT-LEG-STALE-MOUNT=1" +else + echo "ABORT-LEG-STALE-MOUNT=0" +fi +INNER + chmod +x "$inner" + + abort_rc=0 + if command -v timeout >/dev/null 2>&1; then + timeout "$((START_TIMEOUT + TEARDOWN_TIMEOUT))" \ + unshare -Ur -m "$inner" "$AGENTFS_BIN" "$TEST_HOME" "$WORKDIR" "$SESSION_ID" \ + >"$OWNER_LOG" 2>&1 || abort_rc=$? + else + unshare -Ur -m "$inner" "$AGENTFS_BIN" "$TEST_HOME" "$WORKDIR" "$SESSION_ID" \ + >"$OWNER_LOG" 2>&1 || abort_rc=$? + fi + + if grep -q "ABORT-LEG-SETUP-SKIP" "$OWNER_LOG" 2>/dev/null; then + cleanup_current + echo "SKIP: cannot cap max_user_namespaces inside a nested user namespace" + return 0 + fi + + if [ "$abort_rc" -ne 0 ]; then + dump_failure_context "abort-path leg did not complete within $((START_TIMEOUT + TEARDOWN_TIMEOUT))s (rc=$abort_rc)" + kill_abort_leg_stragglers + cleanup_current + return 1 + fi + if ! grep -q "ABORT-LEG-EXIT=1" "$OWNER_LOG"; then + dump_failure_context "abort-path leg: run did not exit 1 after the child handshake failure" + kill_abort_leg_stragglers + cleanup_current + return 1 + fi + if ! grep -q "ABORT-LEG-STALE-MOUNT=0" "$OWNER_LOG"; then + dump_failure_context "abort-path leg: FUSE mount left in the mount table after the parent abort" + kill_abort_leg_stragglers + cleanup_current + return 1 + fi + + cleanup_current + printf " abort-path=OK" + return 0 +} + +# A timed-out abort leg can strand the namespaced agentfs owner (timeout only +# kills the unshare wrapper); reap it by PID via its unique session id. +kill_abort_leg_stragglers() { + ps -e -o pid= -o args= | grep "$SESSION_ID" | grep agentfs | grep -v grep | + while read -r pid _; do + kill -KILL "$pid" 2>/dev/null || true + done +} + +failed=0 +for leg in 1 0; do + if ! run_leg "$leg"; then + failed=1 + fi +done + +if ! run_abort_leg; then + failed=1 +fi + +if [ "$failed" -ne 0 ]; then + exit 1 +fi + +echo " OK" diff --git a/cli/tests/test_fd.c b/crates/agentfs-cli/tests/test_fd.c similarity index 100% rename from cli/tests/test_fd.c rename to crates/agentfs-cli/tests/test_fd.c diff --git a/sdk/rust/.gitignore b/crates/agentfs-core/.gitignore similarity index 100% rename from sdk/rust/.gitignore rename to crates/agentfs-core/.gitignore diff --git a/crates/agentfs-core/Cargo.toml b/crates/agentfs-core/Cargo.toml new file mode 100644 index 00000000..442eec3c --- /dev/null +++ b/crates/agentfs-core/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "agentfs-core" +version.workspace = true +edition.workspace = true +description = "AgentFS SDK for Rust" +license.workspace = true + +[lints] +workspace = true + +[dependencies] +turso = { workspace = true } +tokio = { workspace = true } +async-trait = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +libc = { workspace = true } +thiserror = { workspace = true } +lru = { workspace = true } +tracing = { workspace = true } +parking_lot = { workspace = true } +smallvec = { workspace = true } + +[target.'cfg(target_os = "macos")'.dependencies] +aegis = { workspace = true } + +[dev-dependencies] +tempfile = { workspace = true } +proptest = { workspace = true } +criterion = { workspace = true } +rand = { workspace = true } + +[[bench]] +name = "overlayfs" +harness = false + +[[bench]] +name = "workload" +harness = false + diff --git a/sdk/rust/benches/overlayfs.rs b/crates/agentfs-core/benches/overlayfs.rs similarity index 91% rename from sdk/rust/benches/overlayfs.rs rename to crates/agentfs-core/benches/overlayfs.rs index e3290446..0156eed2 100644 --- a/sdk/rust/benches/overlayfs.rs +++ b/crates/agentfs-core/benches/overlayfs.rs @@ -2,11 +2,13 @@ //! //! Run with: cargo bench --bench overlayfs -use agentfs_sdk::filesystem::{AgentFS, FileSystem, HostFS, OverlayFS}; +use agentfs_core::fs::{AgentFS, FileSystem, HostFS, OverlayFS}; use criterion::{criterion_group, criterion_main, Criterion}; use std::sync::Arc; use tempfile::tempdir; +const ROOT_INO: i64 = 1; + fn bench_remove_file(c: &mut Criterion) { let rt = tokio::runtime::Runtime::new().unwrap(); @@ -40,7 +42,7 @@ fn bench_remove_file(c: &mut Criterion) { }, |(overlay, _base_dir, _delta_dir)| { rt.block_on(async { - let _ = overlay.remove("/test.txt").await; + let _ = overlay.unlink(ROOT_INO, "test.txt").await; }); }, criterion::BatchSize::SmallInput, diff --git a/sdk/rust/benches/workload.rs b/crates/agentfs-core/benches/workload.rs similarity index 86% rename from sdk/rust/benches/workload.rs rename to crates/agentfs-core/benches/workload.rs index c5a00bd5..3481e84f 100644 --- a/sdk/rust/benches/workload.rs +++ b/crates/agentfs-core/benches/workload.rs @@ -5,13 +5,17 @@ //! //! Run with: cargo bench --bench workload -use agentfs_sdk::filesystem::{AgentFS, FileSystem, HostFS, OverlayFS}; +use agentfs_core::fs::{ + AgentFS, FileSystem, HostFS, OverlayFS, DEFAULT_DIR_MODE, DEFAULT_FILE_MODE, +}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use rand::distributions::WeightedIndex; use rand::prelude::*; use std::sync::Arc; use tempfile::tempdir; +const ROOT_INO: i64 = 1; + /// Operation types that can be performed on the filesystem. #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Operation { @@ -302,27 +306,80 @@ impl WorkloadGenerator { } } +async fn resolve_path(overlay: &OverlayFS, path: &str) -> agentfs_core::error::Result> { + let mut ino = ROOT_INO; + for component in path.trim_matches('/').split('/').filter(|c| !c.is_empty()) { + let Some(stats) = overlay.lookup(ino, component).await? else { + return Ok(None); + }; + ino = stats.ino; + } + Ok(Some(ino)) +} + +fn split_parent_name(path: &str) -> Option<(String, String)> { + let mut components: Vec<&str> = path + .trim_matches('/') + .split('/') + .filter(|component| !component.is_empty()) + .collect(); + let name = components.pop()?.to_string(); + let parent = if components.is_empty() { + "/".to_string() + } else { + format!("/{}", components.join("/")) + }; + Some((parent, name)) +} + +async fn resolve_parent( + overlay: &OverlayFS, + path: &str, +) -> agentfs_core::error::Result> { + let Some((parent, name)) = split_parent_name(path) else { + return Ok(None); + }; + let Some(parent_ino) = resolve_path(overlay, &parent).await? else { + return Ok(None); + }; + Ok(Some((parent_ino, name))) +} + /// Execute a single operation on the overlay filesystem. async fn execute_operation(overlay: &OverlayFS, op: Operation, path: &str) { match op { Operation::CreateFile => { // Ignore errors - path may not exist, which is expected - let _ = overlay.create_file(path, 0o100644, 0, 0).await; + if let Ok(Some((parent_ino, name))) = resolve_parent(overlay, path).await { + let _ = overlay + .create_file(parent_ino, &name, DEFAULT_FILE_MODE, 0, 0) + .await; + } } Operation::Lstat => { - let _ = overlay.lstat(path).await; + let _ = resolve_path(overlay, path).await; } Operation::Mkdir => { - let _ = overlay.mkdir(path, 0, 0).await; + if let Ok(Some((parent_ino, name))) = resolve_parent(overlay, path).await { + let _ = overlay + .mkdir(parent_ino, &name, DEFAULT_DIR_MODE, 0, 0) + .await; + } } Operation::Open => { - let _ = overlay.open(path).await; + if let Ok(Some(ino)) = resolve_path(overlay, path).await { + let _ = overlay.open(ino, libc::O_RDONLY).await; + } } Operation::ReaddirPlus => { - let _ = overlay.readdir_plus(path).await; + if let Ok(Some(ino)) = resolve_path(overlay, path).await { + let _ = overlay.readdir_plus(ino).await; + } } Operation::Stat => { - let _ = overlay.stat(path).await; + if let Ok(Some(ino)) = resolve_path(overlay, path).await { + let _ = overlay.getattr(ino).await; + } } } } diff --git a/crates/agentfs-core/src/config/core.rs b/crates/agentfs-core/src/config/core.rs new file mode 100644 index 00000000..105ccef6 --- /dev/null +++ b/crates/agentfs-core/src/config/core.rs @@ -0,0 +1,117 @@ +use std::time::Duration; + +use crate::fs::PartialOriginPolicy; + +use super::EnvReader; + +pub const DEFAULT_CHUNK_SIZE: usize = 65_536; +pub const DEFAULT_INLINE_THRESHOLD: usize = 16_384; +pub const DEFAULT_WRITE_BATCH_MS: u64 = 5; +pub const DEFAULT_WRITE_BATCH_BYTES: usize = 4 * 1024 * 1024; +pub const DEFAULT_WRITE_BATCH_GLOBAL_BYTES: usize = 64 * 1024 * 1024; +pub const DEFAULT_WRITE_BATCH_TXN_INODES: usize = 1024; +pub const DEFAULT_WRITE_BATCH_TXN_BYTES: usize = 32 * 1024 * 1024; + +const WRITE_BATCHER_MS_ENV: &str = "AGENTFS_BATCH_MS"; +const WRITE_BATCHER_BYTES_ENV: &str = "AGENTFS_BATCH_BYTES"; +const WRITE_BATCHER_GLOBAL_BYTES_ENV: &str = "AGENTFS_BATCH_GLOBAL_BYTES"; +const WRITE_BATCHER_TXN_INODES_ENV: &str = "AGENTFS_BATCH_TXN_INODES"; +const WRITE_BATCHER_TXN_BYTES_ENV: &str = "AGENTFS_BATCH_TXN_BYTES"; +const OVERLAY_READS_ENV: &str = "AGENTFS_OVERLAY_READS"; +const DRAIN_ON_SETATTR_ENV: &str = "AGENTFS_DRAIN_ON_SETATTR"; +const KEEPCACHE_DELTA_ENV: &str = "AGENTFS_KEEPCACHE_DELTA"; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Geometry { + pub chunk_size: usize, + pub inline_threshold: usize, +} + +impl Default for Geometry { + fn default() -> Self { + Self { + chunk_size: DEFAULT_CHUNK_SIZE, + inline_threshold: DEFAULT_INLINE_THRESHOLD, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BatcherConfig { + pub enabled: bool, + pub window: Duration, + pub inode_bytes: usize, + pub global_bytes: usize, + pub txn_max_inodes: usize, + pub txn_max_bytes: usize, +} + +impl Default for BatcherConfig { + fn default() -> Self { + Self { + enabled: true, + window: Duration::from_millis(DEFAULT_WRITE_BATCH_MS), + inode_bytes: DEFAULT_WRITE_BATCH_BYTES, + global_bytes: DEFAULT_WRITE_BATCH_GLOBAL_BYTES, + txn_max_inodes: DEFAULT_WRITE_BATCH_TXN_INODES, + txn_max_bytes: DEFAULT_WRITE_BATCH_TXN_BYTES, + } + } +} + +impl BatcherConfig { + pub fn from_env(reader: EnvReader) -> Self { + let default = Self::default(); + Self { + enabled: default.enabled, + window: reader.duration_millis(WRITE_BATCHER_MS_ENV, DEFAULT_WRITE_BATCH_MS), + inode_bytes: reader.positive_usize(WRITE_BATCHER_BYTES_ENV, default.inode_bytes), + global_bytes: reader + .positive_usize(WRITE_BATCHER_GLOBAL_BYTES_ENV, default.global_bytes), + txn_max_inodes: reader + .positive_usize(WRITE_BATCHER_TXN_INODES_ENV, default.txn_max_inodes) + .max(1), + txn_max_bytes: reader + .positive_usize(WRITE_BATCHER_TXN_BYTES_ENV, default.txn_max_bytes) + .max(1), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CoreConfig { + pub geometry: Geometry, + pub batcher: BatcherConfig, + pub overlay_reads: bool, + pub drain_on_setattr: bool, + pub keepcache_delta: bool, + pub partial_origin: PartialOriginPolicy, +} + +impl Default for CoreConfig { + fn default() -> Self { + Self { + geometry: Geometry::default(), + batcher: BatcherConfig::default(), + overlay_reads: true, + drain_on_setattr: true, + keepcache_delta: true, + partial_origin: PartialOriginPolicy::default(), + } + } +} + +impl CoreConfig { + pub fn from_env() -> Self { + let reader = EnvReader::new(); + let default = Self::default(); + Self { + geometry: default.geometry, + batcher: BatcherConfig::from_env(reader), + overlay_reads: reader.bool(OVERLAY_READS_ENV, default.overlay_reads), + drain_on_setattr: reader.bool(DRAIN_ON_SETATTR_ENV, default.drain_on_setattr), + keepcache_delta: reader.bool(KEEPCACHE_DELTA_ENV, default.keepcache_delta), + partial_origin: default.partial_origin, + } + } +} diff --git a/crates/agentfs-core/src/config/env.rs b/crates/agentfs-core/src/config/env.rs new file mode 100644 index 00000000..ff99b51b --- /dev/null +++ b/crates/agentfs-core/src/config/env.rs @@ -0,0 +1,123 @@ +use std::env::VarError; +use std::time::Duration; + +/// Shared environment reader for runtime configuration. +#[derive(Debug, Clone, Copy, Default)] +pub struct EnvReader; + +impl EnvReader { + pub const fn new() -> Self { + Self + } + + /// Read an optional string environment value. + /// + /// Non-Unicode values behave like an unset variable, matching the legacy + /// `std::env::var(...).ok()` behavior at call sites that use ambient + /// credentials or OS context rather than runtime boolean knobs. + pub fn string(self, name: &str) -> Option { + std::env::var(name).ok() + } + + /// Parse the shared boolean grammar: + /// true = {1,true,yes,on}, false = {0,false,no,off}. + pub fn parse_bool(value: &str) -> Option { + match value.trim() { + "1" => Some(true), + "0" => Some(false), + value + if value.eq_ignore_ascii_case("true") + || value.eq_ignore_ascii_case("yes") + || value.eq_ignore_ascii_case("on") => + { + Some(true) + } + value + if value.eq_ignore_ascii_case("false") + || value.eq_ignore_ascii_case("no") + || value.eq_ignore_ascii_case("off") => + { + Some(false) + } + _ => None, + } + } + + pub fn bool(self, name: &str, default: bool) -> bool { + match std::env::var(name) { + Ok(value) => Self::parse_bool(&value).unwrap_or_else(|| { + tracing::warn!( + "Ignoring invalid {}={:?}; using default {}", + name, + value, + default + ); + default + }), + Err(VarError::NotPresent) => default, + Err(VarError::NotUnicode(value)) => { + tracing::warn!( + "Ignoring non-Unicode {}={:?}; using default {}", + name, + value, + default + ); + default + } + } + } + + pub fn duration_millis(self, name: &str, default_ms: u64) -> Duration { + match std::env::var(name) { + Ok(value) => match value.parse::() { + Ok(ms) => Duration::from_millis(ms), + Err(_) => { + tracing::warn!( + "Ignoring invalid {}={:?}; using default {}ms", + name, + value, + default_ms + ); + Duration::from_millis(default_ms) + } + }, + Err(VarError::NotPresent) => Duration::from_millis(default_ms), + Err(VarError::NotUnicode(value)) => { + tracing::warn!( + "Ignoring non-Unicode {}={:?}; using default {}ms", + name, + value, + default_ms + ); + Duration::from_millis(default_ms) + } + } + } + + pub fn positive_usize(self, name: &str, default_value: usize) -> usize { + match std::env::var(name) { + Ok(value) => match value.parse::().ok().filter(|value| *value > 0) { + Some(parsed) => parsed, + None => { + tracing::warn!( + "Ignoring invalid {}={:?}; using default {}", + name, + value, + default_value + ); + default_value + } + }, + Err(VarError::NotPresent) => default_value, + Err(VarError::NotUnicode(value)) => { + tracing::warn!( + "Ignoring non-Unicode {}={:?}; using default {}", + name, + value, + default_value + ); + default_value + } + } + } +} diff --git a/crates/agentfs-core/src/config/mod.rs b/crates/agentfs-core/src/config/mod.rs new file mode 100644 index 00000000..7db7a66f --- /dev/null +++ b/crates/agentfs-core/src/config/mod.rs @@ -0,0 +1,261 @@ +//! Typed runtime configuration for the SDK/core crate. +//! +//! Environment variables are read only in this module. Everything downstream +//! receives typed values through [`CoreConfig`] or narrower config structs. + +mod core; +pub mod env; + +pub use core::{ + BatcherConfig, CoreConfig, Geometry, DEFAULT_CHUNK_SIZE, DEFAULT_INLINE_THRESHOLD, + DEFAULT_WRITE_BATCH_BYTES, DEFAULT_WRITE_BATCH_GLOBAL_BYTES, DEFAULT_WRITE_BATCH_MS, + DEFAULT_WRITE_BATCH_TXN_BYTES, DEFAULT_WRITE_BATCH_TXN_INODES, +}; +pub use env::EnvReader; + +#[cfg(test)] +mod tests { + use super::{CoreConfig, EnvReader}; + use crate::fs::PartialOriginMode; + use std::path::{Path, PathBuf}; + + #[test] + fn runtime_env_bool_grammar_is_shared() { + let covered_runtime_bool_knobs = [ + "AGENTFS_FUSE_WRITEBACK", + "AGENTFS_FUSE_NOOPEN", + "AGENTFS_FUSE_NOFLUSH", + "AGENTFS_FUSE_URING", + "AGENTFS_PROFILE", + "AGENTFS_DRAIN_ON_SETATTR", + ]; + println!( + "covered runtime bool knobs: {}", + covered_runtime_bool_knobs.join(", ") + ); + + for value in ["1", "true", "TRUE", "True", "yes", "YES", "on", "ON"] { + assert_eq!( + EnvReader::parse_bool(value), + Some(true), + "{value} should parse as true" + ); + } + + for value in ["0", "false", "FALSE", "False", "no", "NO", "off", "OFF"] { + assert_eq!( + EnvReader::parse_bool(value), + Some(false), + "{value} should parse as false" + ); + } + + for value in ["", "maybe", "truthy", "2", "enable"] { + assert_eq!( + EnvReader::parse_bool(value), + None, + "{value:?} should fall back to the caller default" + ); + } + + let key = format!("AGENTFS_TEST_BOOL_GRAMMAR_{}", std::process::id()); + std::env::set_var(&key, "garbage"); + let reader = EnvReader::new(); + assert!( + reader.bool(&key, true), + "invalid true-default case should keep the default" + ); + assert!( + !reader.bool(&key, false), + "invalid false-default case should keep the default" + ); + std::env::remove_var(&key); + } + + #[test] + fn env_reader_is_the_only_runtime_env_entry() { + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let repo_root = manifest_dir + .parent() + .and_then(Path::parent) + .expect("crates/agentfs-core should live two levels below the repo root"); + let source_roots = [ + SourceRoot::new( + "crates/agentfs-core/src", + manifest_dir.join("src"), + SourceKind::Sdk, + ), + SourceRoot::new( + "crates/agentfs-cli/src", + repo_root.join("crates").join("agentfs-cli").join("src"), + SourceKind::Cli, + ), + SourceRoot::new( + "crates/agentfs-fuse/src", + repo_root.join("crates").join("agentfs-fuse").join("src"), + SourceKind::Fuse, + ), + ]; + let mut offenders = Vec::new(); + for source_root in &source_roots { + scan_rs_files(source_root, &source_root.path, &mut offenders); + } + assert!( + offenders.is_empty(), + "runtime env reads outside config modules:\n{}", + offenders.join("\n") + ); + } + + struct SourceRoot { + label: &'static str, + path: PathBuf, + kind: SourceKind, + } + + impl SourceRoot { + fn new(label: &'static str, path: PathBuf, kind: SourceKind) -> Self { + Self { label, path, kind } + } + } + + #[derive(Clone, Copy)] + enum SourceKind { + Sdk, + Cli, + Fuse, + } + + fn scan_rs_files(source_root: &SourceRoot, path: &Path, offenders: &mut Vec) { + if path + .components() + .any(|component| component.as_os_str() == "target") + || is_test_path(source_root, path) + || is_config_module(source_root, path) + { + return; + } + + if path.is_dir() { + for entry in std::fs::read_dir(path).expect("source dir should be readable") { + let entry = entry.expect("source entry should be readable"); + scan_rs_files(source_root, &entry.path(), offenders); + } + return; + } + + if path.extension().and_then(|ext| ext.to_str()) != Some("rs") { + return; + } + + let contents = std::fs::read_to_string(path).expect("source file should be readable"); + let mut cfg_test_pending = false; + let mut test_depth = 0usize; + for (line_idx, line) in contents.lines().enumerate() { + if is_test_only_line(line, &mut cfg_test_pending, &mut test_depth) { + continue; + } + if line.contains("std::env::var(") + || line.contains("std::env::var_os(") + || line.contains("env::var(") + || line.contains("env::var_os(") + { + let rel = path.strip_prefix(&source_root.path).unwrap_or(path); + offenders.push(format!( + "{}/{}:{}", + source_root.label, + rel.display(), + line_idx + 1 + )); + } + } + } + + fn is_test_path(source_root: &SourceRoot, path: &Path) -> bool { + let rel = path.strip_prefix(&source_root.path).unwrap_or(path); + rel.components() + .any(|component| component.as_os_str() == "tests") + } + + fn is_config_module(source_root: &SourceRoot, path: &Path) -> bool { + let rel = path.strip_prefix(&source_root.path).unwrap_or(path); + match source_root.kind { + SourceKind::Sdk => rel + .components() + .any(|component| component.as_os_str() == "config"), + SourceKind::Cli => { + rel == Path::new("config.rs") + || rel == Path::new("fuse_config.rs") + || rel + .components() + .next() + .is_some_and(|component| component.as_os_str() == "config") + } + SourceKind::Fuse => rel == Path::new("adapter/config.rs"), + } + } + + fn is_test_only_line(line: &str, cfg_test_pending: &mut bool, test_depth: &mut usize) -> bool { + if *test_depth > 0 { + update_brace_depth(test_depth, line); + return true; + } + + let trimmed = line.trim_start(); + if trimmed.starts_with("#[cfg(test)]") { + *cfg_test_pending = true; + if line.contains('{') { + *test_depth = brace_delta(line).max(0) as usize; + *cfg_test_pending = false; + } + return true; + } + + if *cfg_test_pending { + if line.contains('{') { + *test_depth = brace_delta(line).max(0) as usize; + *cfg_test_pending = false; + } + return true; + } + + false + } + + fn update_brace_depth(depth: &mut usize, line: &str) { + let delta = brace_delta(line); + if delta.is_negative() { + *depth = depth.saturating_sub(delta.unsigned_abs()); + } else { + *depth += delta as usize; + } + } + + fn brace_delta(line: &str) -> isize { + line.chars().fold(0, |depth, ch| match ch { + '{' => depth + 1, + '}' => depth - 1, + _ => depth, + }) + } + + #[test] + fn core_config_ignores_legacy_partial_origin_env() { + let key = concat!("AGENTFS_OVERLAY_", "PARTIAL_ORIGIN"); + let previous = std::env::var(key).ok(); + std::env::set_var(key, "1"); + + let config = CoreConfig::from_env(); + + match previous { + Some(value) => std::env::set_var(key, value), + None => std::env::remove_var(key), + } + + println!( + "legacy partial-origin env ignored; resolved policy is {:?}", + config.partial_origin.mode + ); + assert_eq!(config.partial_origin.mode, PartialOriginMode::Off); + } +} diff --git a/sdk/rust/src/error.rs b/crates/agentfs-core/src/error.rs similarity index 79% rename from sdk/rust/src/error.rs rename to crates/agentfs-core/src/error.rs index 023af5dd..8230d585 100644 --- a/sdk/rust/src/error.rs +++ b/crates/agentfs-core/src/error.rs @@ -3,27 +3,32 @@ use thiserror::Error; /// The main error type for the AgentFS SDK. +/// +/// Wrapper variants chain their cause through `source()` only and keep it out +/// of `Display`: `#[from]` already exposes the inner error to reporters that +/// walk the chain (anyhow `{:#}`), so repeating `{0}` in the message would +/// print every cause twice ("database error: X: X"). #[derive(Debug, Error)] pub enum Error { /// Database error from turso - #[error("database error: {0}")] + #[error("database error")] Database(#[from] turso::Error), /// IO error - #[error("io error: {0}")] + #[error("io error")] Io(#[from] std::io::Error), /// JSON serialization/deserialization error - #[error("json error: {0}")] + #[error("json error")] Json(#[from] serde_json::Error), /// System time error - #[error("time error: {0}")] + #[error("time error")] Time(#[from] std::time::SystemTimeError), /// Filesystem-specific error with errno semantics #[error(transparent)] - Fs(#[from] crate::filesystem::FsError), + Fs(#[from] crate::fs::FsError), /// Invalid agent ID format #[error("invalid agent ID '{0}': agent IDs must contain only alphanumeric characters, hyphens, and underscores")] @@ -33,6 +38,10 @@ pub enum Error { #[error("agent '{id}' not found at '{path}'")] AgentNotFound { id: String, path: String }, + /// Database file path does not exist + #[error("database not found: {0}")] + DatabaseNotFound(String), + /// Invalid path encoding #[error("path '{0}' is not valid UTF-8")] InvalidUtf8Path(String), diff --git a/crates/agentfs-core/src/fs/agentfs/batcher.rs b/crates/agentfs-core/src/fs/agentfs/batcher.rs new file mode 100644 index 00000000..ad1d6af7 --- /dev/null +++ b/crates/agentfs-core/src/fs/agentfs/batcher.rs @@ -0,0 +1,1343 @@ +//! Write batcher: pending-write state plus the `PendingView`/`Drain` split. +//! +//! Lock order: `commit_lock` (tokio `AsyncMutex`) -> `state` (parking_lot +//! `RwLock`). Drains take the commit lock first and only then take short +//! `state` guards to extract owned pending batches; enqueue/peek paths take +//! `state` alone and never acquire `commit_lock`. No sync guard is held +//! across an `.await` (`clippy::await_holding_lock` is deny-by-workspace). + +use async_trait::async_trait; +use parking_lot::RwLock; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use tokio::sync::Mutex as AsyncMutex; +use turso::transaction::{Transaction, TransactionBehavior}; +use turso::{Connection, Value}; + +use crate::config::{BatcherConfig, Geometry}; +use crate::error::{Error, Result}; +use crate::fs::{FsError, Stats, WriteRange}; +use crate::pool::ConnectionPool; + +use super::current_timestamp; +use super::store::{self, normalize_write_ranges, NormalizedWriteRange, WriteRangeRef}; + +pub(super) type Invalidate = Arc; + +#[cfg(test)] +pub(super) const MAX_RETIRED_GENERATIONS: usize = 64; +#[cfg(not(test))] +const MAX_RETIRED_GENERATIONS: usize = 16 * 1024; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) struct PendingGeneration(u64); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) struct OverlayHit { + pub(super) applied: bool, +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub(super) struct EnqueueOutcome { + pub(super) drain_inode: bool, + pub(super) drain_all: bool, +} + +pub(super) type PendingTimes = PendingTimeChange; + +pub(super) trait PendingView: Send + Sync { + fn has_pending(&self, ino: i64) -> bool; + fn overlay_read(&self, ino: i64, off: u64, buf: &mut [u8]) -> OverlayHit; + fn pending_max_end(&self, ino: i64) -> Option; + fn pending_times(&self, ino: i64) -> Option; + fn pending_generation(&self, ino: i64) -> PendingGeneration; + fn merge_into_stats(&self, ino: i64, stats: &mut Stats); +} + +#[async_trait] +pub(super) trait Drain: Send + Sync { + async fn drain_inode(&self, ino: i64) -> Result<()>; + async fn drain_all(&self) -> Result<()>; + async fn drain_inode_bytes(&self, ino: i64) -> Result<()>; + async fn drain_all_bytes(&self) -> Result<()>; + fn enqueue(&self, ino: i64, ranges: Vec) -> Result; + fn discard_pending(&self, ino: i64); + fn mark_times_explicit(&self, ino: i64); + fn truncate_pending(&self, ino: i64, size: u64); + fn stash_times(&self, ino: i64, times: PendingTimes); +} + +#[derive(Clone)] +pub(super) struct BatcherPendingView { + batcher: Arc, +} + +#[derive(Clone)] +pub(super) struct BatcherDrain { + batcher: Arc, +} + +#[derive(Debug, Clone, Copy)] +pub(super) enum AgentFSWriteBatchDrainReason { + Timer, + Bytes, + Explicit, +} + +/// Explicitly-set timestamps stashed by `utimens` while the inode still has +/// pending batched writes. Instead of paying a dedicated SQLite transaction +/// per SETATTR (the FUSE writeback cache sends one per written file during a +/// clone), the values ride along in the pending entry and the batcher applies +/// them inside the SAME drain transaction, right after the data UPDATE — so +/// the explicitly-set times win over the commit-time stamp without an extra +/// per-file transaction. `getattr`/`lookup` overlay these values onto the +/// SQLite row (`merge_pending_view`) so the change is visible immediately. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub(super) struct PendingTimeChange { + /// (secs, nsec) for atime, when explicitly set. + pub(super) atime: Option<(i64, i64)>, + /// (secs, nsec) for mtime, when explicitly set. + pub(super) mtime: Option<(i64, i64)>, + /// (secs, nsec) for ctime (always bumped by the setattr that stashed). + pub(super) ctime: Option<(i64, i64)>, +} + +impl PendingTimeChange { + pub(super) fn is_empty(&self) -> bool { + self.atime.is_none() && self.mtime.is_none() && self.ctime.is_none() + } + + /// Per-field "newer wins" merge: fields set by `newer` override ours. + fn apply(&mut self, newer: &PendingTimeChange) { + if newer.atime.is_some() { + self.atime = newer.atime; + } + if newer.mtime.is_some() { + self.mtime = newer.mtime; + } + if newer.ctime.is_some() { + self.ctime = newer.ctime; + } + } + + /// Drop the fields a buffered data write would re-stamp (mtime/ctime). A + /// write AFTER the explicit setattr means the file changed again, so the + /// eventual commit must stamp fresh modification times; an explicitly-set + /// atime is unaffected by writes and survives. + fn clear_write_stamped(&mut self) { + self.mtime = None; + self.ctime = None; + } + + /// Overlay the stashed values onto a `Stats` row read from SQLite, so + /// `getattr`/`lookup` surface explicit `utimens` results immediately even + /// though the row UPDATE is deferred to the next batched drain. + pub(super) fn merge_into(&self, stats: &mut Stats) { + if let Some((secs, nsec)) = self.atime { + stats.atime = secs; + stats.atime_nsec = nsec as u32; + } + if let Some((secs, nsec)) = self.mtime { + stats.mtime = secs; + stats.mtime_nsec = nsec as u32; + } + if let Some((secs, nsec)) = self.ctime { + stats.ctime = secs; + stats.ctime_nsec = nsec as u32; + } + } +} + +/// Build the time-column SET fragments for a batched data-commit UPDATE so +/// stashed explicit times ride the same statement as size/storage/data +/// (one UPDATE per inode instead of two; ~4,700 extra UPDATEs per clone +/// otherwise). Precedence per column: a stashed explicit value wins; without +/// one, mtime/ctime are stamped with the commit time unless `preserve_times` +/// (an explicit setattr landed after the writes and its values must not be +/// clobbered); atime is only ever written explicitly. +pub(super) fn write_commit_time_sets( + preserve_times: bool, + explicit_times: Option<&PendingTimeChange>, +) -> Result<(Vec<&'static str>, Vec)> { + let explicit_atime = explicit_times.and_then(|t| t.atime); + let explicit_mtime = explicit_times.and_then(|t| t.mtime); + let explicit_ctime = explicit_times.and_then(|t| t.ctime); + let stamp = if !preserve_times && (explicit_mtime.is_none() || explicit_ctime.is_none()) { + Some(current_timestamp()?) + } else { + None + }; + let mut sets = Vec::new(); + let mut values = Vec::new(); + if let Some((secs, nsec)) = explicit_atime { + sets.push("atime = ?"); + values.push(Value::Integer(secs)); + sets.push("atime_nsec = ?"); + values.push(Value::Integer(nsec)); + } + if let Some((secs, nsec)) = explicit_mtime.or(stamp) { + sets.push("mtime = ?"); + values.push(Value::Integer(secs)); + sets.push("mtime_nsec = ?"); + values.push(Value::Integer(nsec)); + } + if let Some((secs, nsec)) = explicit_ctime.or(stamp) { + sets.push("ctime = ?"); + values.push(Value::Integer(secs)); + sets.push("ctime_nsec = ?"); + values.push(Value::Integer(nsec)); + } + Ok((sets, values)) +} + +/// Apply a stashed `PendingTimeChange` to fs_inode using the drain +/// transaction's connection. Used for time-only pending entries (no data +/// ranges to commit, so there is no data UPDATE to fold the times into); +/// runs inside the drain's `BEGIN IMMEDIATE`. A deleted inode simply matches +/// no row (the unlink already won). +async fn apply_pending_times_with_conn( + conn: &Connection, + ino: i64, + times: &PendingTimeChange, +) -> Result<()> { + let mut updates = Vec::new(); + let mut values: Vec = Vec::new(); + if let Some((secs, nsec)) = times.atime { + updates.push("atime = ?"); + values.push(Value::Integer(secs)); + updates.push("atime_nsec = ?"); + values.push(Value::Integer(nsec)); + } + if let Some((secs, nsec)) = times.mtime { + updates.push("mtime = ?"); + values.push(Value::Integer(secs)); + updates.push("mtime_nsec = ?"); + values.push(Value::Integer(nsec)); + } + if let Some((secs, nsec)) = times.ctime { + updates.push("ctime = ?"); + values.push(Value::Integer(secs)); + updates.push("ctime_nsec = ?"); + values.push(Value::Integer(nsec)); + } + if updates.is_empty() { + return Ok(()); + } + values.push(Value::Integer(ino)); + let sql = format!("UPDATE fs_inode SET {} WHERE ino = ?", updates.join(", ")); + conn.execute(&sql, values).await?; + Ok(()) +} + +struct PendingInodeWrites { + ranges: Vec, + pending_bytes: usize, + generation: u64, + /// True when an explicit attribute change (chmod / chown / utimens) was + /// applied to fs_inode AFTER the most recent enqueue for this inode. The + /// deferred data commit normally stamps mtime/ctime with the commit time; + /// when this flag is set it must preserve the explicitly-set times instead + /// (the setattr logically happened after the buffered writes). Reset on + /// every enqueue: a write after the setattr should bump the times again. + times_explicit: bool, + /// Explicit `utimens` values waiting to be committed together with the + /// pending data (see `PendingTimeChange`). Cleared field-wise: a new + /// enqueue drops mtime/ctime (write re-stamps them), the drain clears the + /// values it actually committed. + pending_times: Option, +} + +impl PendingInodeWrites { + fn new(generation: u64) -> Self { + Self { + ranges: Vec::new(), + pending_bytes: 0, + generation, + times_explicit: false, + pending_times: None, + } + } + + fn bump_generation(&mut self, generation: u64) { + self.generation = generation; + } + + /// True when nothing is left to commit for this inode (no data ranges and + /// no stashed explicit times) — only then may the entry be dropped. + fn is_drained(&self) -> bool { + self.ranges.is_empty() && self.pending_times.is_none() + } + + fn push_ranges(&mut self, ranges: Vec, byte_count: usize) -> Result<()> { + self.pending_bytes = self + .pending_bytes + .checked_add(byte_count) + .ok_or_else(|| Error::Internal("batched write byte count overflow".to_string()))?; + self.times_explicit = false; + if let Some(times) = &mut self.pending_times { + times.clear_write_stamped(); + if times.is_empty() { + self.pending_times = None; + } + } + self.ranges.extend(ranges); + Ok(()) + } +} + +#[derive(Clone, Copy)] +struct RetiredGeneration { + generation: u64, + retired_at: u64, +} + +#[derive(Default)] +struct AgentFSWriteBatcherState { + pending: HashMap, + /// Monotonic per-inode version for the pending overlay. Cache fills that + /// read SQLite before a concurrent drain must compare the generation they + /// observed with the current one before inserting attrs. + retired_generations: HashMap, + /// Fallback generation for inodes whose retired entry was pruned. It is + /// advanced only by actual pruning, so never-batched inodes keep the + /// stable zero fallback across unrelated write traffic while pruned + /// retired entries remain ABA-safe. + pruned_generation_watermark: u64, + /// Global monotonic generation counter. This advances for every pending + /// overlay mutation, but absent never-batched inodes do not report it + /// directly (see `pruned_generation_watermark`). + last_generation: u64, + /// Logical retirement epoch used only to bound `retired_generations`. + retired_generation_epoch: u64, + /// Running sum of `pending_bytes` across every inode in `pending`. Kept in + /// lock-step with the map so the enqueue path can enforce a global memory + /// cap in O(1) instead of summing the map on every write. Every site that + /// mutates a `PendingInodeWrites.pending_bytes` or inserts/removes an entry + /// must keep this consistent (see `debug_assert_total`). + total_pending_bytes: usize, + /// True while the single coalescing drain scheduler task is armed (see + /// `run_drain_scheduler`). Set by the enqueue that arms it, cleared by the + /// scheduler itself under this same lock once nothing is pending — so an + /// enqueue either observes it set (the running scheduler will pick the new + /// write up) or arms a fresh scheduler. Pending work is never stranded. + drain_scheduled: bool, +} + +impl AgentFSWriteBatcherState { + fn generation(&self, ino: i64) -> PendingGeneration { + PendingGeneration( + self.pending + .get(&ino) + .map(|entry| entry.generation) + .or_else(|| { + self.retired_generations + .get(&ino) + .map(|retired| retired.generation) + }) + .unwrap_or(self.pruned_generation_watermark), + ) + } + + fn bump_generation(&mut self, ino: i64) { + let generation = self.next_generation(); + if let Some(entry) = self.pending.get_mut(&ino) { + entry.bump_generation(generation); + } else { + self.retire_generation(ino, generation); + } + } + + fn next_generation(&mut self) -> u64 { + self.last_generation = self + .last_generation + .checked_add(1) + .expect("AgentFS write batcher generation counter exhausted"); + self.last_generation + } + + fn retire_generation(&mut self, ino: i64, generation: u64) { + self.retired_generation_epoch = self + .retired_generation_epoch + .checked_add(1) + .expect("AgentFS write batcher retired-generation epoch exhausted"); + self.retired_generations.insert( + ino, + RetiredGeneration { + generation, + retired_at: self.retired_generation_epoch, + }, + ); + self.prune_retired_generations(); + } + + fn prune_retired_generations(&mut self) { + while self.retired_generations.len() > MAX_RETIRED_GENERATIONS { + let Some(oldest_ino) = self + .retired_generations + .iter() + .min_by_key(|(_, retired)| retired.retired_at) + .map(|(ino, _)| *ino) + else { + break; + }; + if let Some(pruned) = self.retired_generations.remove(&oldest_ino) { + // Advance only to the pruned entry's own generation, never to + // last_generation: a fill for a still-tracked ino compares + // against its retained entry, so ABA safety needs the + // watermark to cover exactly what was pruned. Jumping to + // last_generation would suppress never-batched fills whenever + // any prune lands between observe and compare at saturation. + self.pruned_generation_watermark = + self.pruned_generation_watermark.max(pruned.generation); + } + } + } + + #[cfg(debug_assertions)] + fn debug_assert_total(&self) { + let sum: usize = self.pending.values().map(|b| b.pending_bytes).sum(); + debug_assert_eq!( + sum, self.total_pending_bytes, + "batcher total_pending_bytes drifted from sum of pending entries" + ); + } + + #[cfg(not(debug_assertions))] + #[inline] + fn debug_assert_total(&self) {} +} + +/// In-memory write group-commit queue for FUSE writeback mode. +/// +/// The batcher stores only transient `WriteRange` values and drains them into +/// the canonical SQLite tables. It never creates sidecars and normal durability +/// boundaries (`flush`, `fsync`, `release`, `destroy`) explicitly drain it. +pub(super) struct AgentFSWriteBatcher { + pool: ConnectionPool, + chunk_size: usize, + inline_threshold: usize, + invalidate: Invalidate, + batch_ms: Duration, + batch_bytes: usize, + batch_global_bytes: usize, + /// Per-transaction inode-count bound for batched drains + /// (`AGENTFS_BATCH_TXN_INODES`). See `drain_pending_batched`. + txn_max_inodes: usize, + /// Per-transaction pending-bytes bound for batched drains + /// (`AGENTFS_BATCH_TXN_BYTES`). See `drain_pending_batched`. + txn_max_bytes: usize, + /// Tier 4 mitigation: parking_lot `RwLock` so `peek_pending` / + /// `peek_pending_max_end` can acquire read-only access without contending + /// with writers. The lock is never held across an `.await`, so a sync + /// lock is safe inside async fns. Holding it across an await would block + /// the tokio worker — `take_pending_locked` and friends always extract + /// owned state under the lock and drop the guard before any I/O. + state: RwLock, + commit_lock: AsyncMutex<()>, +} + +impl AgentFSWriteBatcher { + pub(super) fn split(batcher: &Arc) -> (BatcherPendingView, BatcherDrain) { + ( + BatcherPendingView { + batcher: Arc::clone(batcher), + }, + BatcherDrain { + batcher: Arc::clone(batcher), + }, + ) + } + + pub(super) fn from_config( + pool: ConnectionPool, + chunk_size: usize, + inline_threshold: usize, + invalidate: Invalidate, + config: &BatcherConfig, + ) -> Self { + Self { + pool, + chunk_size, + inline_threshold, + invalidate, + batch_ms: config.window, + batch_bytes: config.inode_bytes, + batch_global_bytes: config.global_bytes, + txn_max_inodes: config.txn_max_inodes.max(1), + txn_max_bytes: config.txn_max_bytes.max(1), + state: RwLock::new(AgentFSWriteBatcherState::default()), + commit_lock: AsyncMutex::new(()), + } + } + + fn queue(self: &Arc, ino: i64, ranges: Vec) -> Result { + let ranges: Vec<_> = ranges + .into_iter() + .filter(|range| !range.data.is_empty()) + .collect(); + if ranges.is_empty() { + return Ok(EnqueueOutcome::default()); + } + + let byte_count = ranges.iter().try_fold(0usize, |acc, range| { + acc.checked_add(range.data.len()) + .ok_or_else(|| Error::Internal("batched write byte count overflow".to_string())) + })?; + let drain_now; + let mut drain_all_now = false; + let mut schedule_drain = false; + + { + let mut state = self.state.write(); + drain_now = { + let generation = state.next_generation(); + let entry = state + .pending + .entry(ino) + .or_insert_with(|| PendingInodeWrites::new(generation)); + entry.push_ranges(ranges, byte_count)?; + entry.bump_generation(generation); + crate::telemetry::record_agentfs_batcher_enqueue(); + crate::telemetry::record_agentfs_batcher_pending_bytes(entry.pending_bytes as u64); + + entry.pending_bytes >= self.batch_bytes + }; + state.total_pending_bytes = state.total_pending_bytes.saturating_add(byte_count); + // Global memory ceiling: a single full batched drain is cheaper and + // frees more memory than draining just this inode, so it takes + // precedence over the per-inode trigger. + if state.total_pending_bytes >= self.batch_global_bytes { + drain_all_now = true; + } + // Group commit: arm the single coalescing drain scheduler if it is + // not already running, instead of one timer task per inode (which + // degenerated into a storm of small serialized transactions during + // a clone burst). + if !state.drain_scheduled { + state.drain_scheduled = true; + schedule_drain = true; + } + state.debug_assert_total(); + } + + // Tier Four: invalidate the attr cache as soon as a write is queued, + // not just when the batch commits to SQLite. getattr ORs in + // peek_pending_max_end so the size view stays correct, but other + // consumers (mtime/ctime, link count assumptions) must not see a + // cached pre-write attr after a successful pwrite returns. + (self.invalidate)(ino); + + if schedule_drain { + self.spawn_drain_scheduler(); + } + + Ok(EnqueueOutcome { + drain_inode: drain_now && !drain_all_now, + drain_all: drain_all_now, + }) + } + + #[cfg(test)] + pub(super) async fn enqueue_for_test( + self: &Arc, + ino: i64, + ranges: Vec, + ) -> Result<()> { + let outcome = self.queue(ino, ranges)?; + if outcome.drain_all { + self.drain_all_reason(AgentFSWriteBatchDrainReason::Bytes) + .await?; + } else if outcome.drain_inode { + self.drain_pending_batched(AgentFSWriteBatchDrainReason::Bytes, Some(ino)) + .await?; + } + Ok(()) + } + + pub(super) async fn drain_all_reason( + self: &Arc, + reason: AgentFSWriteBatchDrainReason, + ) -> Result<()> { + // Always batch on full drain: destroy / finalize / public AgentFS::drain_all. + loop { + self.drain_pending_batched(reason, None).await?; + let still_pending = { + let state = self.state.read(); + !state.pending.is_empty() + }; + if !still_pending { + return Ok(()); + } + } + } + + /// Drain currently-pending inode batches inside a single SQLite + /// transaction. Holds one connection and one `BEGIN IMMEDIATE` / `COMMIT` + /// pair across all per-inode chunk writes, so every drain trigger shares + /// the same commit-then-remove discipline. + /// + /// One transaction is bounded by `txn_max_inodes` / `txn_max_bytes` + /// (`AGENTFS_BATCH_TXN_INODES` / `AGENTFS_BATCH_TXN_BYTES`); when the + /// pending map exceeds the bound the call commits a bounded subset and + /// returns `Ok(true)` so the caller immediately drains again + /// (back-to-back transactions) instead of building one unbounded txn. + /// Returns `Ok(false)` when everything that was pending at snapshot time + /// has been committed. + /// + /// `required_ino` lets per-inode drains (Bytes cap / explicit fsync, + /// release, forget, setattr paths) express their caller contract: "the + /// writes queued for this inode must be durable when this returns". If the + /// inode is not in pending when we take the snapshot, it was committed by a + /// concurrent drain and the contract is already met. If it IS pending, it + /// is always selected into this transaction regardless of the + /// per-transaction bounds. + pub(super) async fn drain_pending_batched( + self: &Arc, + reason: AgentFSWriteBatchDrainReason, + required_ino: Option, + ) -> Result { + let _commit_guard = self.commit_lock.lock().await; + + // Tier 4 corruption fix (commit-then-remove): SNAPSHOT pending ranges + // by cloning, WITHOUT removing them from the overlay. `pread`/`getattr` + // consult the overlay and then SQLite with no lock spanning the two; if + // we removed the ranges here (as the original `mem::take` did), a read + // landing between the take and `txn.commit` would find the write in + // NEITHER the overlay nor committed SQLite and return stale data + // (the intermittent git-clone corruption). Leaving the overlay + // populated until after the commit guarantees every write is always + // visible in the overlay OR in SQLite. + let (snapshot, more_pending): (Vec<(i64, Vec)>, bool) = { + let state = self.state.read(); + let mut selected: Vec<(i64, Vec)> = Vec::new(); + let mut selected_bytes = 0usize; + let mut truncated = false; + // The per-inode drain contract inode is always part of this + // transaction, independent of the bounds. + if let Some(req) = required_ino { + if let Some(batch) = state.pending.get(&req) { + if !batch.ranges.is_empty() || batch.pending_times.is_some() { + selected_bytes = selected_bytes.saturating_add(batch.pending_bytes); + selected.push((req, batch.ranges.clone())); + } + } + } + for (ino, batch) in state.pending.iter() { + if Some(*ino) == required_ino { + continue; + } + if batch.ranges.is_empty() && batch.pending_times.is_none() { + continue; + } + // Bound the transaction by inode count and pending bytes; the + // first selected inode is always admitted so progress is + // guaranteed even when a single inode exceeds the byte bound. + if selected.len() >= self.txn_max_inodes + || (!selected.is_empty() + && selected_bytes.saturating_add(batch.pending_bytes) > self.txn_max_bytes) + { + truncated = true; + break; + } + selected_bytes = selected_bytes.saturating_add(batch.pending_bytes); + selected.push((*ino, batch.ranges.clone())); + } + (selected, truncated) + }; + + if snapshot.is_empty() { + self.cleanup_empty_pending(); + return Ok(false); + } + + // (ino, committed_raw_range_count, normalized ranges to write). + // Entries with an empty range list are still included: they carry + // stashed explicit times (`pending_times`) that must be committed in + // this transaction even though there is no data to write. + let mut to_commit: Vec<(i64, usize, Vec)> = + Vec::with_capacity(snapshot.len()); + for (ino, ranges) in &snapshot { + let range_refs: Vec<_> = ranges + .iter() + .map(|range| WriteRangeRef { + offset: range.offset, + data: range.data.as_slice(), + }) + .collect(); + // On normalize error the overlay is left intact (nothing removed), + // so the ranges are simply retried on the next drain. + let normalized = normalize_write_ranges(&range_refs)?; + if !normalized.is_empty() { + crate::telemetry::record_agentfs_batcher_coalesced_ranges( + ranges.len().saturating_sub(normalized.len()) as u64, + ); + // Per-inode drain accounting (one tick per inode whose DATA we + // actually commit, matching the old reporting cardinality — + // time-only commits are not counted as drains). + match reason { + AgentFSWriteBatchDrainReason::Timer => { + crate::telemetry::record_agentfs_batcher_drain_timer(); + } + AgentFSWriteBatchDrainReason::Bytes => { + crate::telemetry::record_agentfs_batcher_drain_bytes(); + } + AgentFSWriteBatchDrainReason::Explicit => { + crate::telemetry::record_agentfs_batcher_drain_explicit(); + } + } + } + to_commit.push((*ino, ranges.len(), normalized)); + } + + if to_commit.is_empty() { + self.cleanup_empty_pending(); + return Ok(more_pending); + } + + let started = Instant::now(); + let conn = self.pool.get_connection().await?; + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + + // Read times_explicit and the stashed explicit times only AFTER the + // IMMEDIATE transaction holds the SQLite write lock: explicit + // chmod/chown/utimens that already landed marked the flag / stash + // before their effect, later ones are blocked behind us (or stay + // stashed for the next drain). + let (preserve_times, pending_times): (HashMap, HashMap) = { + let state = self.state.read(); + let preserve = to_commit + .iter() + .map(|(ino, _, _)| { + ( + *ino, + state + .pending + .get(ino) + .map(|batch| batch.times_explicit) + .unwrap_or(false), + ) + }) + .collect(); + let times = to_commit + .iter() + .filter_map(|(ino, _, _)| { + state + .pending + .get(ino) + .and_then(|batch| batch.pending_times) + .map(|t| (*ino, t)) + }) + .collect(); + (preserve, times) + }; + + // Stashed times we actually applied inside this transaction; cleared + // from the pending entries only after the commit succeeds. + let mut applied_times: Vec<(i64, PendingTimeChange)> = Vec::new(); + + for (ino, _count, normalized) in &to_commit { + let mut inode_missing = false; + if !normalized.is_empty() { + let normalized_refs: Vec<_> = normalized + .iter() + .map(|range| WriteRangeRef { + offset: range.offset, + data: range.data.as_slice(), + }) + .collect(); + let geometry = Geometry { + chunk_size: self.chunk_size, + inline_threshold: self.inline_threshold, + }; + match store::write_ranges( + &conn, + *ino, + geometry, + &normalized_refs, + preserve_times.get(ino).copied().unwrap_or(false), + pending_times.get(ino), + ) + .await + { + Ok(()) => {} + // The file was unlinked / renamed-over while its writes were + // still pending (git lock and temp files routinely live and + // die within the batch window). Its data is moot: skip it and + // let the post-commit cleanup drop the orphaned ranges + // instead of aborting the whole multi-inode batch. + Err(Error::Fs(FsError::NotFound)) => { + tracing::debug!( + "AgentFS write batcher: dropping pending writes for deleted inode {}", + ino + ); + inode_missing = true; + } + Err(error) => { + let _ = txn.rollback().await; + // Overlay was never modified; ranges remain pending and are + // retried on the next drain. No restore needed. + return Err(error); + } + } + } + + // Stashed explicit times ride the data UPDATE above + // (`write_commit_time_sets`); a time-only entry has no data UPDATE + // to fold into, so it pays one standalone UPDATE inside this same + // transaction. + if !inode_missing { + if let Some(times) = pending_times.get(ino) { + if normalized.is_empty() { + if let Err(error) = apply_pending_times_with_conn(&conn, *ino, times).await + { + let _ = txn.rollback().await; + return Err(error); + } + } + applied_times.push((*ino, *times)); + } + } + } + + txn.commit().await?; + + // Durable now: drop exactly the committed ranges and applied times + // from the overlay, preserving anything enqueued during the commit. + for (ino, times) in &applied_times { + self.clear_applied_times(*ino, times); + } + let committed_counts: Vec<(i64, usize)> = to_commit + .iter() + .map(|(ino, count, _)| (*ino, *count)) + .collect(); + self.remove_committed_prefix(&committed_counts); + for (ino, _, _) in &to_commit { + (self.invalidate)(*ino); + } + self.cleanup_empty_pending(); + // Anything still pending (ranges enqueued during the commit, inodes + // beyond the per-txn bound, stashed times that arrived mid-commit) is + // never stranded: the coalescing scheduler picks it up on its next + // pass. Arm it if it is not already running (e.g. explicit drains + // triggered by fsync / kill-switch paths outside the scheduler). + self.ensure_drain_scheduled(); + crate::telemetry::record_agentfs_batcher_commit_latency(started.elapsed()); + crate::telemetry::record_agentfs_batcher_commit_txn(to_commit.len() as u64); + + Ok(more_pending) + } + + /// Arm the single coalescing drain scheduler if it is not already armed + /// and there is pending work left. Cheap safety net used after drains so + /// residual pending state (ranges enqueued mid-commit, stashed times, + /// inodes beyond a bounded transaction) always has a scheduled commit. + fn ensure_drain_scheduled(self: &Arc) { + let arm = { + let mut state = self.state.write(); + if !state.drain_scheduled && !state.pending.is_empty() { + state.drain_scheduled = true; + true + } else { + false + } + }; + if arm { + self.spawn_drain_scheduler(); + } + } + + /// Spawn the coalescing drain scheduler task. Exactly one instance runs + /// while `state.drain_scheduled` is true; it exits (and clears the flag) + /// only once nothing is pending. + fn spawn_drain_scheduler(self: &Arc) { + let batcher = Arc::clone(self); + tokio::spawn(async move { + batcher.run_drain_scheduler().await; + }); + } + + /// Single coalescing drain scheduler (cross-inode group commit). + /// + /// Instead of one timer task per written inode — which degenerated into a + /// storm of small, serialized SQLite transactions during a git-clone burst + /// — ONE task is armed when the first pending write arrives. Each cycle it + /// sleeps `AGENTFS_BATCH_MS` so concurrent writers coalesce, then commits + /// everything that is pending at that instant in as few `BEGIN IMMEDIATE` + /// transactions as the per-transaction bounds allow + /// (`AGENTFS_BATCH_TXN_INODES` / `AGENTFS_BATCH_TXN_BYTES`), back-to-back. + /// Writes that arrive while a commit is in flight are picked up by the + /// next cycle. The task exits only when nothing is pending; an enqueue + /// that observes `drain_scheduled == false` arms a fresh one. Explicit + /// drains (fsync, finalize, kill-switch paths) and the Bytes triggers are + /// unaffected — they keep draining synchronously on their own call sites. + async fn run_drain_scheduler(self: Arc) { + loop { + tokio::time::sleep(self.batch_ms).await; + + // One pass: commit everything pending at this instant, splitting + // into bounded back-to-back transactions when over the per-txn + // bounds. On error the overlay is left intact (commit-then-remove) + // and the pass is retried on the next cycle. + loop { + match self + .drain_pending_batched(AgentFSWriteBatchDrainReason::Timer, None) + .await + { + Ok(true) => continue, + Ok(false) => break, + Err(error) => { + tracing::warn!( + ?error, + "AgentFS write batcher: scheduled group drain failed (will retry)" + ); + break; + } + } + } + + // Exit only when nothing is pending. The flag is cleared under the + // same write lock that observes the empty map, so a concurrent + // enqueue either sees the flag still set (this loop continues and + // commits it) or sees it cleared and arms a fresh scheduler. + let exit = { + let mut state = self.state.write(); + if state.pending.is_empty() { + state.drain_scheduled = false; + true + } else { + false + } + }; + if exit { + return; + } + } + } + + /// Tier 4 corruption fix: after a drain has durably committed a snapshot of + /// pending ranges to SQLite, drop exactly those ranges from the overlay. + /// Snapshots are taken from the front of each inode's append-only `ranges` + /// vec, and enqueues only ever append, so the committed ranges are the first + /// `count` entries; ranges appended during the commit are preserved. The + /// `.min(len)` guard tolerates a concurrent `truncate_pending`/`discard_pending` + /// having shrunk or removed the entry. Entries that still have ranges or + /// stashed explicit times are kept; the coalescing scheduler (or the + /// caller's `ensure_drain_scheduled`) commits them on a later pass. + fn remove_committed_prefix(&self, committed: &[(i64, usize)]) { + let mut state = self.state.write(); + for &(ino, count) in committed { + let (removed_bytes, empty, should_bump) = { + let Some(entry) = state.pending.get_mut(&ino) else { + continue; + }; + let n = count.min(entry.ranges.len()); + let removed_bytes: usize = entry.ranges.drain(..n).map(|r| r.data.len()).sum(); + entry.pending_bytes = entry.pending_bytes.saturating_sub(removed_bytes); + // An entry with stashed explicit times but no ranges is NOT + // drained yet — keep it so a later drain commits the times. + let empty = entry.is_drained(); + (removed_bytes, empty, removed_bytes > 0 || empty) + }; + let generation = if should_bump { + let generation = state.next_generation(); + if let Some(entry) = state.pending.get_mut(&ino) { + entry.bump_generation(generation); + } + Some(generation) + } else { + None + }; + state.total_pending_bytes = state.total_pending_bytes.saturating_sub(removed_bytes); + if empty { + if let Some(generation) = generation { + state.retire_generation(ino, generation); + } + state.pending.remove(&ino); + } + } + state.debug_assert_total(); + } + + /// Remove pending entries that have nothing left to commit (no ranges and + /// no stashed explicit times). Clears their attr cache entry too. + fn cleanup_empty_pending(&self) { + let removed: Vec = { + let mut state = self.state.write(); + let empties: Vec = state + .pending + .iter() + .filter(|(_, b)| b.is_drained()) + .map(|(ino, _)| *ino) + .collect(); + for ino in &empties { + if let Some(b) = state.pending.remove(ino) { + state.total_pending_bytes = + state.total_pending_bytes.saturating_sub(b.pending_bytes); + state.retire_generation(*ino, b.generation); + } + } + state.debug_assert_total(); + empties + }; + for ino in removed { + (self.invalidate)(ino); + } + } + + // ----- Tier Four: in-memory overlay read API ----- + // + // These methods let `AgentFSFile::pread` / `getattr` / `truncate` consult + // the batcher's pending state directly, instead of forcing a synchronous + // SQLite drain for read-after-write consistency. The drain becomes a + // pure durability operation, only triggered by explicit `fsync` / + // destroy / timer / bytes triggers. + + /// Snapshot pending writes for `ino` overlapping `[offset, offset+size)`. + /// Returned ranges are normalised (non-overlapping, sorted) and clipped + /// to the requested window. The batcher's pending state is not modified. + /// Callers merge the result over SQLite data with "pending wins" + /// semantics; see `AgentFSFile::pread`. + fn pending_ranges(&self, ino: i64, offset: u64, size: u64) -> Vec { + if size == 0 { + return Vec::new(); + } + let read_end = match offset.checked_add(size) { + Some(end) => end, + None => return Vec::new(), + }; + // Read-lock: many concurrent readers OK; writers block briefly during + // enqueue. Crucially, no `.await` is performed while the guard is + // held, so a sync `parking_lot::RwLock` is safe inside an async fn. + let state = self.state.read(); + let Some(batch) = state.pending.get(&ino) else { + return Vec::new(); + }; + if batch.ranges.is_empty() { + return Vec::new(); + } + let refs: Vec<_> = batch + .ranges + .iter() + .map(|r| WriteRangeRef { + offset: r.offset, + data: r.data.as_slice(), + }) + .collect(); + let normalized = match normalize_write_ranges(&refs) { + Ok(n) => n, + Err(_) => return Vec::new(), + }; + normalized + .into_iter() + .filter_map(|range| { + let r_end = range.offset + range.data.len() as u64; + if r_end <= offset || range.offset >= read_end { + return None; + } + let clip_start = offset.max(range.offset); + let clip_end = read_end.min(r_end); + if clip_end <= clip_start { + return None; + } + let skip = (clip_start - range.offset) as usize; + let take = (clip_end - clip_start) as usize; + Some(NormalizedWriteRange { + offset: clip_start, + data: range.data[skip..skip + take].to_vec(), + }) + }) + .collect() + } + + /// Fast path for "does this inode have ANY pending write?" — used by + /// readers to skip the heavier `peek_pending_max_end` / `peek_pending` + /// calls entirely when the batcher has nothing for the inode. Read lock, + /// O(1) HashMap hit. + pub(super) fn has_pending(&self, ino: i64) -> bool { + let state = self.state.read(); + state + .pending + .get(&ino) + .map(|b| !b.ranges.is_empty()) + .unwrap_or(false) + } + + /// Largest write end (offset + length) for `ino` across all pending + /// ranges. Returns `None` if no pending writes for this inode. Callers + /// OR this with the SQLite-stored `fs_inode.size` to compute the + /// file-size view exposed to readers (so a write that grows the file is + /// visible to subsequent `getattr` even before the timer drain commits + /// it to SQLite). + pub(super) fn pending_max_end_raw(&self, ino: i64) -> Option { + let state = self.state.read(); + let batch = state.pending.get(&ino)?; + batch + .ranges + .iter() + .map(|r| r.offset.saturating_add(r.data.len() as u64)) + .max() + } + + /// Record that an explicit attribute change (chmod / chown / utimens) was + /// applied to fs_inode for `ino` after the writes currently pending for + /// it. The eventual data commit must then preserve mtime/ctime instead of + /// stamping the commit time (see `drain_pending_batched`). No-op when the + /// inode has nothing pending — there is no deferred commit to clobber the + /// attributes in that case. + pub(super) fn mark_times_explicit(&self, ino: i64) { + let mut state = self.state.write(); + if let Some(batch) = state.pending.get_mut(&ino) { + batch.times_explicit = true; + state.bump_generation(ino); + } + } + + /// Stash explicitly-set `utimens` values in the inode's pending entry so + /// the next batched drain commits them inside the SAME transaction as any + /// pending data (one extra UPDATE statement, zero dedicated foreground + /// transactions). The entry is created when the inode has nothing pending + /// yet: the kernel's writeback SETATTR routinely lands after the inode's + /// data already drained (or before its flush arrives), and creating the + /// entry both removes the per-file fallback transaction and guarantees a + /// scheduled drain applies the times. `merge_pending_view` keeps the + /// change visible to getattr/lookup immediately. + pub(super) fn stash_pending_times(self: &Arc, ino: i64, change: PendingTimeChange) { + if change.is_empty() { + return; + } + { + let mut state = self.state.write(); + let generation = state.next_generation(); + state + .pending + .entry(ino) + .or_insert_with(|| PendingInodeWrites::new(generation)) + .pending_times + .get_or_insert_with(PendingTimeChange::default) + .apply(&change); + if let Some(entry) = state.pending.get_mut(&ino) { + entry.bump_generation(generation); + } + } + (self.invalidate)(ino); + // A times-only entry still needs a scheduled drain to commit it. + self.ensure_drain_scheduled(); + } + + /// Snapshot the stashed explicit times for `ino` (if any) without removing + /// them. Drains read this AFTER their `BEGIN IMMEDIATE` holds the write + /// lock and clear exactly what they applied once the commit succeeds + /// (commit-then-remove, mirroring the data-range discipline). Readers use + /// it to overlay not-yet-committed times onto fs_inode rows. + pub(super) fn pending_times_raw(&self, ino: i64) -> Option { + let state = self.state.read(); + state.pending.get(&ino).and_then(|b| b.pending_times) + } + + pub(super) fn pending_generation_raw(&self, ino: i64) -> PendingGeneration { + let state = self.state.read(); + state.generation(ino) + } + + /// After a successful commit, drop the stashed time fields that were + /// actually applied. Field-wise equality keeps any NEWER stash that + /// arrived while the transaction was in flight (it will be committed by + /// the next drain instead of being silently lost). + fn clear_applied_times(&self, ino: i64, applied: &PendingTimeChange) { + let mut state = self.state.write(); + let Some(batch) = state.pending.get_mut(&ino) else { + return; + }; + let Some(times) = &mut batch.pending_times else { + return; + }; + if times.atime == applied.atime { + times.atime = None; + } + if times.mtime == applied.mtime { + times.mtime = None; + } + if times.ctime == applied.ctime { + times.ctime = None; + } + if times.is_empty() { + batch.pending_times = None; + } + state.bump_generation(ino); + } + + /// Drop any pending bytes beyond `new_size` and shrink ranges that span + /// the truncation boundary. Called by `AgentFSFile::truncate` so the + /// overlay agrees with the post-truncate file state without needing to + /// drain first. + pub(super) fn truncate_pending(&self, ino: i64, new_size: u64) { + let mut state = self.state.write(); + if !state.pending.contains_key(&ino) { + return; + } + let generation = state.next_generation(); + let (old_bytes, new_bytes, now_empty) = { + let Some(batch) = state.pending.get_mut(&ino) else { + return; + }; + let old_bytes = batch.pending_bytes; + let mut new_bytes = 0usize; + batch.ranges.retain_mut(|range| { + let r_end = range.offset.saturating_add(range.data.len() as u64); + if range.offset >= new_size { + return false; + } + if r_end > new_size { + let keep = (new_size - range.offset) as usize; + range.data.truncate(keep); + } + new_bytes = new_bytes.saturating_add(range.data.len()); + !range.data.is_empty() + }); + batch.pending_bytes = new_bytes; + batch.bump_generation(generation); + (old_bytes, new_bytes, batch.ranges.is_empty()) + }; + state.total_pending_bytes = state + .total_pending_bytes + .saturating_sub(old_bytes) + .saturating_add(new_bytes); + if now_empty { + state.retire_generation(ino, generation); + state.pending.remove(&ino); + } + state.debug_assert_total(); + } + + /// Discard every pending write for a reaped inode once the deletion is + /// committed (or before a deferred reap transaction opens). This prevents + /// stale pending data from surviving past the inode's logical lifetime. + pub(super) fn discard_pending(&self, ino: i64) { + let mut state = self.state.write(); + if let Some(batch) = state.pending.remove(&ino) { + state.total_pending_bytes = state + .total_pending_bytes + .saturating_sub(batch.pending_bytes); + let generation = state.next_generation(); + state.retire_generation(ino, generation); + } + state.debug_assert_total(); + } + + #[cfg(test)] + pub(super) fn total_pending_bytes(&self) -> usize { + self.state.read().total_pending_bytes + } + + #[cfg(test)] + pub(super) fn retired_generation_count(&self) -> usize { + self.state.read().retired_generations.len() + } + + #[cfg(test)] + pub(super) fn retired_generation_contains(&self, ino: i64) -> bool { + self.state.read().retired_generations.contains_key(&ino) + } + + /// Retire an explicit generation at a fresh epoch, reproducing the + /// epoch/generation inversion `cleanup_empty_pending` creates when a + /// long-lived pending entry retires with its own (older) generation. + #[cfg(test)] + pub(super) fn retire_generation_for_test(&self, ino: i64, generation: u64) { + self.state.write().retire_generation(ino, generation); + } +} + +impl PendingView for BatcherPendingView { + fn has_pending(&self, ino: i64) -> bool { + self.batcher.has_pending(ino) + } + + fn overlay_read(&self, ino: i64, off: u64, buf: &mut [u8]) -> OverlayHit { + let mut applied = false; + for range in self.batcher.pending_ranges(ino, off, buf.len() as u64) { + let dst_off = (range.offset - off) as usize; + if dst_off >= buf.len() { + continue; + } + let end = (dst_off + range.data.len()).min(buf.len()); + buf[dst_off..end].copy_from_slice(&range.data[..end - dst_off]); + applied = true; + } + OverlayHit { applied } + } + + fn pending_max_end(&self, ino: i64) -> Option { + self.batcher.pending_max_end_raw(ino) + } + + fn pending_times(&self, ino: i64) -> Option { + self.batcher.pending_times_raw(ino) + } + + fn pending_generation(&self, ino: i64) -> PendingGeneration { + self.batcher.pending_generation_raw(ino) + } + + fn merge_into_stats(&self, ino: i64, stats: &mut Stats) { + if let Some(times) = self.pending_times(ino) { + times.merge_into(stats); + } + if let Some(pending_end) = self.pending_max_end(ino) { + let pending_end_i64 = i64::try_from(pending_end).unwrap_or(i64::MAX); + if pending_end_i64 > stats.size { + stats.size = pending_end_i64; + } + } + } +} + +#[async_trait] +impl Drain for BatcherDrain { + async fn drain_inode(&self, ino: i64) -> Result<()> { + self.batcher + .drain_pending_batched(AgentFSWriteBatchDrainReason::Explicit, Some(ino)) + .await?; + Ok(()) + } + + async fn drain_all(&self) -> Result<()> { + self.batcher + .drain_all_reason(AgentFSWriteBatchDrainReason::Explicit) + .await + } + + async fn drain_inode_bytes(&self, ino: i64) -> Result<()> { + self.batcher + .drain_pending_batched(AgentFSWriteBatchDrainReason::Bytes, Some(ino)) + .await?; + Ok(()) + } + + async fn drain_all_bytes(&self) -> Result<()> { + self.batcher + .drain_all_reason(AgentFSWriteBatchDrainReason::Bytes) + .await + } + + fn enqueue(&self, ino: i64, ranges: Vec) -> Result { + self.batcher.queue(ino, ranges) + } + + fn discard_pending(&self, ino: i64) { + self.batcher.discard_pending(ino); + } + + fn mark_times_explicit(&self, ino: i64) { + self.batcher.mark_times_explicit(ino); + } + + fn truncate_pending(&self, ino: i64, size: u64) { + self.batcher.truncate_pending(ino, size); + } + + fn stash_times(&self, ino: i64, times: PendingTimes) { + self.batcher.stash_pending_times(ino, times); + } +} diff --git a/crates/agentfs-core/src/fs/agentfs/caches.rs b/crates/agentfs-core/src/fs/agentfs/caches.rs new file mode 100644 index 00000000..6b1e9abf --- /dev/null +++ b/crates/agentfs-core/src/fs/agentfs/caches.rs @@ -0,0 +1,157 @@ +//! Bounded dentry, negative-dentry, and attr caches for AgentFS. +//! +//! The cache layer is intentionally synchronous and conservative: namespace +//! and metadata mutations invalidate affected keys before reporting success, +//! while lookups update profiling counters for hot-path visibility. + +use lru::LruCache; +use std::num::NonZeroUsize; +use std::sync::Mutex; + +use super::Stats; + +/// LRU cache for directory entry lookups. +/// +/// Maps (parent_ino, name) -> child_ino to avoid repeated database queries +/// during path resolution. For a path like `/a/b/c/d`, this reduces queries +/// from 4 to potentially 0 on cache hits. +pub(super) struct DentryCache { + // Mutex required because LruCache::get() mutates internal order + entries: Mutex>, +} + +impl DentryCache { + pub(super) fn new(max_size: usize) -> Self { + Self { + entries: Mutex::new(LruCache::new( + NonZeroUsize::new(max_size).expect("cache size must be > 0"), + )), + } + } + + /// Look up a cached entry (updates LRU order) + pub(super) fn get(&self, parent_ino: i64, name: &str) -> Option { + let entry = self + .entries + .lock() + .unwrap() + .get(&(parent_ino, name.to_string())) + .copied(); + if entry.is_some() { + crate::telemetry::record_dentry_cache_hit(); + crate::telemetry::record_path_cache_hit(); + } else { + crate::telemetry::record_dentry_cache_miss(); + crate::telemetry::record_path_cache_miss(); + } + entry + } + + /// Insert an entry into the cache (evicts LRU entry if full) + pub(super) fn insert(&self, parent_ino: i64, name: &str, child_ino: i64) { + self.entries + .lock() + .unwrap() + .put((parent_ino, name.to_string()), child_ino); + } + + /// Remove an entry from the cache + pub(super) fn remove(&self, parent_ino: i64, name: &str) { + self.entries + .lock() + .unwrap() + .pop(&(parent_ino, name.to_string())); + } +} + +/// LRU cache for safe negative directory entry lookups. +/// +/// A negative entry means "this (parent, name) did not exist in the last +/// serialized AgentFS view". Every namespace mutation invalidates exactly the +/// affected key before the mutation reports success, so cached ENOENT results +/// cannot hide later creates or renames made through this filesystem. +pub(super) struct NegativeDentryCache { + entries: Mutex>, +} + +impl NegativeDentryCache { + pub(super) fn new(max_size: usize) -> Self { + Self { + entries: Mutex::new(LruCache::new( + NonZeroUsize::new(max_size).expect("cache size must be > 0"), + )), + } + } + + pub(super) fn contains(&self, parent_ino: i64, name: &str) -> bool { + let cached = self + .entries + .lock() + .unwrap() + .get(&(parent_ino, name.to_string())) + .is_some(); + if cached { + crate::telemetry::record_negative_cache_hit(); + } else { + crate::telemetry::record_negative_cache_miss(); + } + cached + } + + pub(super) fn insert(&self, parent_ino: i64, name: &str) { + self.entries + .lock() + .unwrap() + .put((parent_ino, name.to_string()), ()); + } + + pub(super) fn remove(&self, parent_ino: i64, name: &str) { + if self + .entries + .lock() + .unwrap() + .pop(&(parent_ino, name.to_string())) + .is_some() + { + crate::telemetry::record_negative_cache_invalidation(); + } + } +} + +/// LRU cache for inode attributes. +/// +/// FUSE and SDK stat-heavy read paths often ask for the same inode metadata +/// repeatedly after lookup/readdir_plus. This cache is conservative: every +/// namespace, metadata, or size/content mutation invalidates the affected inode +/// and parent directory entries before the mutation is considered complete. +pub(super) struct AttrCache { + entries: Mutex>, +} + +impl AttrCache { + pub(super) fn new(max_size: usize) -> Self { + Self { + entries: Mutex::new(LruCache::new( + NonZeroUsize::new(max_size).expect("cache size must be > 0"), + )), + } + } + + pub(super) fn get(&self, ino: i64) -> Option { + let stats = self.entries.lock().unwrap().get(&ino).cloned(); + if stats.is_some() { + crate::telemetry::record_attr_cache_hit(); + } else { + crate::telemetry::record_attr_cache_miss(); + } + stats + } + + pub(super) fn insert(&self, stats: Stats) { + self.entries.lock().unwrap().put(stats.ino, stats); + } + + pub(super) fn remove(&self, ino: i64) { + self.entries.lock().unwrap().pop(&ino); + } +} diff --git a/crates/agentfs-core/src/fs/agentfs/file.rs b/crates/agentfs-core/src/fs/agentfs/file.rs new file mode 100644 index 00000000..848ae748 --- /dev/null +++ b/crates/agentfs-core/src/fs/agentfs/file.rs @@ -0,0 +1,309 @@ +//! Open-file handle implementation for AgentFS. +//! +//! `AgentFSFile` owns per-handle read, write, truncate, fsync, and fstat +//! behavior. It overlays pending batched writes onto committed SQLite state +//! without forcing drains on read paths, preserving the noopen/writeback hot +//! path. + +use async_trait::async_trait; +use turso::transaction::{Transaction, TransactionBehavior}; + +use crate::fs::{File, FsError, Stats, WriteRange}; + +use super::batcher::EnqueueOutcome; +use super::store::WriteRangeRef; +use super::*; + +/// An open file handle for AgentFS. +/// +/// This struct holds the inode number resolved at open time, allowing +/// efficient read/write/fsync operations without path lookups. +pub struct AgentFSFile { + pub(super) pool: ConnectionPool, + pub(super) ino: i64, + pub(super) chunk_size: usize, + pub(super) inline_threshold: usize, + pub(super) attr_cache: Arc, + pub(super) pending_view: Option, + pub(super) write_drain: Option, + /// Same semantics as the field on `AgentFS`; cloned at open time so the + /// hot read/write path doesn't have to chase an extra indirection. + pub(super) overlay_reads: bool, + /// Present for user-visible handles so unlink defers inode reaping while + /// they live. This remains optional until lifecycle extraction flattens + /// the handle construction API. + pub(super) _open_guard: Option, +} + +#[async_trait] +impl File for AgentFSFile { + async fn pread(&self, offset: u64, size: u64) -> Result> { + // Tier Four: NO `drain_writes()` prelude. Read SQLite-resident bytes + // (committed state) and overlay pending writes from the in-memory + // batcher snapshot. Together they form a read-after-write consistent + // view without forcing a SQLite commit on the read path. + // + // Ordering matters: peek the batcher state BEFORE acquiring a pool + // connection, and release the connection BEFORE the splice loop. Long + // pread workloads (parallel git-grep) saturate the 8-slot pool, and + // holding a connection across `state.lock().await` starves the timer + // drain task that also needs a connection to commit. + if size == 0 { + return Ok(Vec::new()); + } + // Escape hatch: when overlay reads are disabled, behave like Tier 3 + // — drain the inode's pending writes before reading SQLite. Same + // wire result, slower but battle-tested. + if !self.overlay_reads { + self.drain_writes().await?; + } + let pending_max_end = match &self.pending_view { + Some(view) if self.overlay_reads && view.has_pending(self.ino) => { + view.pending_max_end(self.ino) + } + _ => None, + }; + + let conn = self.pool.get_connection().await?; + let metadata = store::file_storage(&conn, self.ino).await?; + let effective_size = match pending_max_end { + Some(end) => metadata.size.max(end), + None => metadata.size, + }; + + if offset >= effective_size { + return Ok(Vec::new()); + } + let read_size = size.min(effective_size - offset); + + let base_window = if offset < metadata.size { + (metadata.size - offset).min(read_size) + } else { + 0 + }; + let mut result = if base_window > 0 { + let mut buf = store::read_from_storage( + &conn, + self.ino, + self.geometry(), + &metadata, + offset, + base_window, + ) + .await?; + buf.resize(read_size as usize, 0); + buf + } else { + vec![0u8; read_size as usize] + }; + drop(conn); + + if let Some(view) = &self.pending_view { + if pending_max_end.is_some() { + let _ = view.overlay_read(self.ino, offset, &mut result); + } + } + + Ok(result) + } + + async fn pwrite(&self, offset: u64, data: &[u8]) -> Result<()> { + if data.is_empty() { + return Ok(()); + } + // Tier Four: with the batcher wired AND overlay reads enabled, + // route through enqueue so the overlay holds the write and readers + // see it via `pread`'s peek_pending merge. Drain only on + // fsync/destroy/timer. When `AGENTFS_OVERLAY_READS=0` the + // overlay-reads escape hatch is engaged: skip the batcher and commit + // directly so the legacy Tier 3 read path (which drains before + // reading) sees the write. + if let Some(drain) = &self.write_drain { + if self.overlay_reads { + let outcome = drain.enqueue( + self.ino, + vec![WriteRange { + offset, + data: data.to_vec(), + }], + )?; + return Self::finish_enqueue(drain, self.ino, outcome).await; + } + } + // Fallback (no batcher): direct commit. drain_writes is a no-op + // when there's no batcher, but keeping the call here makes the + // contract explicit. + self.drain_writes().await?; + let conn = self.pool.get_connection().await?; + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let ranges = [WriteRangeRef { offset, data }]; + let result = + store::write_ranges(&conn, self.ino, self.geometry(), &ranges, false, None).await; + match result { + Ok(()) => { + txn.commit().await?; + self.attr_cache.remove(self.ino); + Ok(()) + } + Err(e) => { + let _ = txn.rollback().await; + Err(e) + } + } + } + + async fn pwrite_ranges(&self, ranges: Vec) -> Result<()> { + if ranges.iter().all(|range| range.data.is_empty()) { + return Ok(()); + } + // Tier Four: route through the batcher when overlay reads are + // enabled; otherwise commit immediately (escape hatch — see pwrite). + if let Some(drain) = &self.write_drain { + if self.overlay_reads { + let outcome = drain.enqueue(self.ino, ranges)?; + return Self::finish_enqueue(drain, self.ino, outcome).await; + } + } + self.drain_writes().await?; + + let conn = self.pool.get_connection().await?; + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let range_refs: Vec<_> = ranges + .iter() + .map(|range| WriteRangeRef { + offset: range.offset, + data: range.data.as_slice(), + }) + .collect(); + let result = + store::write_ranges(&conn, self.ino, self.geometry(), &range_refs, false, None).await; + match result { + Ok(()) => { + txn.commit().await?; + self.attr_cache.remove(self.ino); + Ok(()) + } + Err(e) => { + let _ = txn.rollback().await; + Err(e) + } + } + } + + async fn pwrite_ranges_batched(&self, ranges: Vec) -> Result<()> { + if ranges.iter().all(|range| range.data.is_empty()) { + return Ok(()); + } + + if let Some(drain) = &self.write_drain { + let outcome = drain.enqueue(self.ino, ranges)?; + Self::finish_enqueue(drain, self.ino, outcome).await + } else { + self.pwrite_ranges(ranges).await + } + } + + async fn truncate(&self, new_size: u64) -> Result<()> { + // Tier Four: shrink the in-memory overlay BEFORE touching SQLite, so + // a concurrent reader doesn't observe pending bytes past the new EOF + // between the SQLite truncate and the batcher catching up. + if let Some(drain) = &self.write_drain { + drain.truncate_pending(self.ino, new_size); + } + // Drain remaining pending so the SQLite truncate sees a consistent + // size. With truncate_pending called above, the only pending left is + // for offsets < new_size, which will be applied by the timer / next + // drain trigger. We still drain here so the SQLite size after this + // call exactly matches `new_size`. + self.drain_writes().await?; + let conn = self.pool.get_connection().await?; + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let result = store::truncate(&conn, self.ino, self.geometry(), new_size).await; + match result { + Ok(()) => { + txn.commit().await?; + self.attr_cache.remove(self.ino); + Ok(()) + } + Err(e) => { + let _ = txn.rollback().await; + Err(e) + } + } + } + + async fn fsync(&self) -> Result<()> { + // Tier Four: fsync remains the explicit durability barrier — drain the + // batcher so the WAL checkpoint that follows captures every pending + // write. + self.drain_writes().await?; + let conn = self.pool.get_connection().await?; + conn.prepare_cached(DURABLE_SYNCHRONOUS_SQL) + .await? + .execute(()) + .await?; + checkpoint_wal(&conn).await?; + conn.prepare_cached(BASELINE_SYNCHRONOUS_SQL) + .await? + .execute(()) + .await?; + Ok(()) + } + + async fn fstat(&self) -> Result { + self.drain_writes().await?; + if let Some(stats) = self.attr_cache.get(self.ino) { + return Ok(stats); + } + + let conn = self.pool.get_connection().await?; + let generation = self + .pending_view + .as_ref() + .map(|view| view.pending_generation(self.ino)); + let mut stmt = conn + .prepare_cached("SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime, rdev, atime_nsec, mtime_nsec, ctime_nsec FROM fs_inode WHERE ino = ?") + .await?; + let mut rows = stmt.query((self.ino,)).await?; + + if let Some(row) = rows.next().await? { + let stats = store::stats_from_row(&row)?; + if let (Some(view), Some(generation)) = (&self.pending_view, generation) { + if view.pending_generation(stats.ino) == generation { + self.attr_cache.insert(stats.clone()); + } + } else { + self.attr_cache.insert(stats.clone()); + } + Ok(stats) + } else { + Err(FsError::NotFound.into()) + } + } + + async fn drain_writes(&self) -> Result<()> { + if let Some(drain) = &self.write_drain { + drain.drain_inode(self.ino).await?; + } + Ok(()) + } +} + +impl AgentFSFile { + async fn finish_enqueue(drain: &BatcherDrain, ino: i64, outcome: EnqueueOutcome) -> Result<()> { + if outcome.drain_all { + drain.drain_all_bytes().await + } else if outcome.drain_inode { + drain.drain_inode_bytes(ino).await + } else { + Ok(()) + } + } + + fn geometry(&self) -> Geometry { + Geometry { + chunk_size: self.chunk_size, + inline_threshold: self.inline_threshold, + } + } +} diff --git a/crates/agentfs-core/src/fs/agentfs/fs.rs b/crates/agentfs-core/src/fs/agentfs/fs.rs new file mode 100644 index 00000000..3c653876 --- /dev/null +++ b/crates/agentfs-core/src/fs/agentfs/fs.rs @@ -0,0 +1,1586 @@ +//! Canonical `FileSystem` trait implementation for AgentFS. +//! +//! This module is the only AgentFS mutation implementation. Path helpers, CLI +//! surfaces, FUSE, and NFS resolve into these inode-oriented operations so +//! namespace, metadata, lifecycle, and batcher semantics cannot diverge. + +use async_trait::async_trait; +use std::collections::HashSet; +use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; + +use turso::transaction::{Transaction, TransactionBehavior}; +use turso::Value; + +use crate::error::Error; +use crate::fs::{DirEntry, DirEntryPage, FileSystem, TimeChange}; + +use super::batcher::PendingTimeChange; +use super::*; + +#[async_trait] +impl FileSystem for AgentFS { + async fn lookup(&self, parent_ino: i64, name: &str) -> Result> { + crate::telemetry::record_lookup(); + if name.len() > MAX_NAME_LEN { + return Err(FsError::NameTooLong.into()); + } + + // Connection-free fast paths via the in-memory caches. These are the + // same caches (and invalidation semantics) that `lookup_child` already + // trusts; consulting them BEFORE acquiring a pool connection avoids a + // wasted acquire/release on every cache hit. This is the clone hot + // path: `OverlayFS::resolve_delta_parent` does O(depth) negative + // delta-parent probes per base-layer lookup, all of which are negative + // cache hits that previously each took a connection. + if name != ".." { + if self.negative_dentry_cache.contains(parent_ino, name) { + crate::telemetry::record_negative_lookup(); + return Ok(None); + } + if let Some(child_ino) = self.dentry_cache.get(parent_ino, name) { + if let Some(mut stats) = self.attr_cache.get(child_ino) { + self.merge_pending_view(child_ino, Some(&mut stats)); + return Ok(Some(stats)); + } + } + } + + let conn = self.pool.get_connection().await?; + + // Handle ".." by finding the parent of parent_ino + if name == ".." { + if parent_ino == ROOT_INO { + // Root's parent is itself + return self.getattr_with_conn(&conn, ROOT_INO).await; + } + let mut stmt = conn + .prepare_cached("SELECT parent_ino FROM fs_dentry WHERE ino = ? LIMIT 1") + .await?; + let mut rows = stmt.query((parent_ino,)).await?; + let parent = if let Some(row) = rows.next().await? { + row.get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(ROOT_INO) + } else { + ROOT_INO + }; + return self.getattr_with_conn(&conn, parent).await; + } + + // Look up the child inode + let child_ino = match self.lookup_child(&conn, parent_ino, name).await? { + Some(ino) => ino, + None => { + crate::telemetry::record_negative_lookup(); + return Ok(None); + } + }; + let generation = self.pending_generation(child_ino); + // Tier Four: do NOT call `drain_inode_writes` here. The single- + // connection ephemeral pool (and even the file-backed pool under + // contention) would deadlock — we already hold the only connection + // permit, and `drain_inode_writes` -> `drain_pending_batched` tries + // to acquire one. Read SQLite, then merge the batcher's pending + // max-end into the size field the same way `getattr` does. + + // Get stats for the child inode + let mut stmt = conn + .prepare_cached("SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime, rdev, atime_nsec, mtime_nsec, ctime_nsec FROM fs_inode WHERE ino = ?") + .await?; + let mut rows = stmt.query((child_ino,)).await?; + + if let Some(row) = rows.next().await? { + let mut stats = store::stats_from_row(&row)?; + self.merge_pending_view(child_ino, Some(&mut stats)); + // Cache the lookup result + self.cache_dentry(parent_ino, name, child_ino); + self.cache_attr_if_pending_generation(stats.clone(), generation); + Ok(Some(stats)) + } else { + Ok(None) + } + } + + async fn getattr(&self, ino: i64) -> Result> { + crate::telemetry::record_getattr(); + // Connection-free fast path: an attr-cache hit needs no pool connection. + // The cache is invalidated on every write (enqueue removes the entry), + // so a hit means there is no uncommitted pending write to merge; the + // merge below is therefore an idempotent no-op but is kept for safety. + // Same cache `getattr_with_conn` already trusts, consulted before the + // acquire. + if let Some(mut stats) = self.attr_cache.get(ino) { + self.merge_pending_view(ino, Some(&mut stats)); + return Ok(Some(stats)); + } + // Tier Four: don't drain — read SQLite metadata and OR in the + // batcher's peek_pending_max_end so the size view reflects pending + // writes that haven't been committed yet. Refresh the attr cache + // with the merged size so subsequent direct cache reads agree with + // what we just returned. + let conn = self.pool.get_connection().await?; + self.getattr_with_conn(&conn, ino).await + } + + /// DB-backed regular files qualify for `FOPEN_KEEP_CACHE`: every mutation + /// path through a mount is kernel-originated (the kernel's pages stay + /// coherent for its own writes) and the adapter's fingerprint guard + /// revalidates mtime/ctime/size at each open, so out-of-band SDK writers + /// are caught exactly like external edits to host-backed base files. + /// The keepcache-delta kill switch restores the old policy where only + /// host-backed base-layer files were eligible. + async fn keep_cache_for_read_open(&self, ino: i64, flags: i32) -> Result> { + if (flags & libc::O_ACCMODE) != libc::O_RDONLY || (flags & libc::O_TRUNC) != 0 { + return Ok(None); + } + if !self.core_config.keepcache_delta { + return Ok(None); + } + let Some(stats) = FileSystem::getattr(self, ino).await? else { + return Ok(None); + }; + Ok(stats.is_file().then_some(stats)) + } + + fn delta_keep_cache_fast_path(&self) -> bool { + self.core_config.keepcache_delta + } + + async fn readlink(&self, ino: i64) -> Result> { + let conn = self.pool.get_connection().await?; + + // Check if the inode exists and is a symlink + if let Some(mode) = store::mode(&conn, ino).await? { + if (mode & S_IFMT) != S_IFLNK { + return Err(FsError::NotASymlink.into()); + } + } else { + return Ok(None); + } + + // Read target from fs_symlink table + let mut stmt = conn + .prepare_cached("SELECT target FROM fs_symlink WHERE ino = ?") + .await?; + let mut rows = stmt.query((ino,)).await?; + + if let Some(row) = rows.next().await? { + let target = row + .get_value(0) + .ok() + .and_then(|v| match v { + Value::Text(s) => Some(s.to_string()), + _ => None, + }) + .ok_or(FsError::InvalidPath)?; + Ok(Some(target)) + } else { + Ok(None) + } + } + + async fn readdir(&self, ino: i64) -> Result>> { + crate::telemetry::record_readdir(); + let conn = self.pool.get_connection().await?; + + // Check if inode exists and is a directory + if let Some(mode) = store::mode(&conn, ino).await? { + if (mode & S_IFMT) != super::S_IFDIR { + return Err(FsError::NotADirectory.into()); + } + } else { + return Ok(None); + } + + let mut stmt = conn + .prepare_cached("SELECT name FROM fs_dentry WHERE parent_ino = ? ORDER BY name") + .await?; + let mut rows = stmt.query((ino,)).await?; + + let mut entries = Vec::new(); + while let Some(row) = rows.next().await? { + let name = row + .get_value(0) + .ok() + .and_then(|v| { + if let Value::Text(s) = v { + Some(s.clone()) + } else { + None + } + }) + .unwrap_or_default(); + if !name.is_empty() { + entries.push(name); + } + } + + Ok(Some(entries)) + } + + async fn readdir_plus(&self, ino: i64) -> Result>> { + crate::telemetry::record_readdir_plus(); + self.drain_all().await?; + let conn = self.pool.get_connection().await?; + + // Check if inode exists and is a directory + if let Some(mode) = store::mode(&conn, ino).await? { + if (mode & S_IFMT) != super::S_IFDIR { + return Err(FsError::NotADirectory.into()); + } + } else { + return Ok(None); + } + + let mut stmt = conn.prepare_cached("SELECT d.id, d.name, i.ino, i.mode, i.nlink, i.uid, i.gid, i.size, i.atime, i.mtime, i.ctime, i.rdev, i.atime_nsec, i.mtime_nsec, i.ctime_nsec + FROM fs_dentry d + JOIN fs_inode i ON d.ino = i.ino + WHERE d.parent_ino = ? + ORDER BY d.name" + ).await?; + let mut rows = stmt.query((ino,)).await?; + + let mut entries = Vec::new(); + while let Some(row) = rows.next().await? { + let cookie = row.get::(0)?; + let name = row + .get_value(1) + .ok() + .and_then(|v| { + if let Value::Text(s) = v { + Some(s.clone()) + } else { + None + } + }) + .unwrap_or_default(); + + if name.is_empty() { + continue; + } + + let stats = store::stats_from_row_at(&row, 2)?; + + self.cache_attr(stats.clone()); + entries.push(DirEntry { + name, + stats, + cookie, + }); + } + + Ok(Some(entries)) + } + + async fn readdir_plus_after( + &self, + ino: i64, + start_after: i64, + max_entries: usize, + ) -> Result> { + crate::telemetry::record_readdir_plus(); + self.drain_all().await?; + let conn = self.pool.get_connection().await?; + + // Check if inode exists and is a directory + if let Some(mode) = store::mode(&conn, ino).await? { + if (mode & S_IFMT) != super::S_IFDIR { + return Err(FsError::NotADirectory.into()); + } + } else { + return Ok(None); + } + + let start_after_name = if start_after > 0 { + let mut stmt = conn + .prepare_cached("SELECT name FROM fs_dentry WHERE id = ? AND parent_ino = ?") + .await?; + let mut rows = stmt.query((start_after, ino)).await?; + match rows.next().await? { + Some(row) => Some( + row.get_value(0) + .ok() + .and_then(|v| match v { + Value::Text(s) => Some(s.clone()), + _ => None, + }) + .unwrap_or_default(), + ), + None => return Err(FsError::BadCookie.into()), + } + } else { + None + }; + + let fetch_limit = max_entries.saturating_add(1).min(i64::MAX as usize) as i64; + let mut stmt = if start_after_name.is_some() { + conn.prepare_cached( + "SELECT d.id, d.name, i.ino, i.mode, i.nlink, i.uid, i.gid, i.size, i.atime, i.mtime, i.ctime, i.rdev, i.atime_nsec, i.mtime_nsec, i.ctime_nsec + FROM fs_dentry d + JOIN fs_inode i ON d.ino = i.ino + WHERE d.parent_ino = ? AND d.name > ? + ORDER BY d.parent_ino, d.name + LIMIT ?", + ) + .await? + } else { + conn.prepare_cached( + "SELECT d.id, d.name, i.ino, i.mode, i.nlink, i.uid, i.gid, i.size, i.atime, i.mtime, i.ctime, i.rdev, i.atime_nsec, i.mtime_nsec, i.ctime_nsec + FROM fs_dentry d + JOIN fs_inode i ON d.ino = i.ino + WHERE d.parent_ino = ? + ORDER BY d.parent_ino, d.name + LIMIT ?", + ) + .await? + }; + + let mut rows = if let Some(start_after_name) = start_after_name { + stmt.query((ino, start_after_name, fetch_limit)).await? + } else { + stmt.query((ino, fetch_limit)).await? + }; + + let mut entries = Vec::new(); + while let Some(row) = rows.next().await? { + let cookie = row.get::(0)?; + let name = row + .get_value(1) + .ok() + .and_then(|v| { + if let Value::Text(s) = v { + Some(s.clone()) + } else { + None + } + }) + .unwrap_or_default(); + + if name.is_empty() { + continue; + } + + let stats = store::stats_from_row_at(&row, 2)?; + + self.cache_attr(stats.clone()); + entries.push(DirEntry { + name, + stats, + cookie, + }); + } + + let end = entries.len() <= max_entries; + entries.truncate(max_entries); + Ok(Some(DirEntryPage { entries, end })) + } + + async fn chmod(&self, ino: i64, mode: u32) -> Result<()> { + self.prepare_attr_change(ino).await?; + let conn = self.pool.get_connection().await?; + // BEGIN IMMEDIATE so this serialises with concurrent batcher drain + // transactions instead of racing them as an autocommit statement + // (turso reports such write/write races as "database snapshot is + // stale" instead of waiting on the write lock). + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let result: Result<()> = async { + // Get current mode to preserve file type bits + let current_mode = store::mode(&conn, ino).await?.ok_or(FsError::NotFound)?; + + // Preserve file type bits (upper bits), replace permission bits (lower 12 bits) + let new_mode = (current_mode & S_IFMT) | (mode & 0o7777); + + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + let now_secs = dur.as_secs() as i64; + let now_nsec = dur.subsec_nanos() as i64; + let mut stmt = conn + .prepare_cached( + "UPDATE fs_inode SET mode = ?, ctime = ?, ctime_nsec = ? WHERE ino = ?", + ) + .await?; + stmt.execute((new_mode as i64, now_secs, now_nsec, ino)) + .await?; + Ok(()) + } + .await; + + match result { + Ok(()) => { + txn.commit().await?; + self.invalidate_attr(ino); + Ok(()) + } + Err(error) => { + let _ = txn.rollback().await; + Err(error) + } + } + } + + async fn chown(&self, ino: i64, uid: Option, gid: Option) -> Result<()> { + if uid.is_none() && gid.is_none() { + return Ok(()); + } + self.prepare_attr_change(ino).await?; + + let conn = self.pool.get_connection().await?; + // BEGIN IMMEDIATE: see `chmod` — avoid autocommit write/write races + // with concurrent batcher drain transactions. + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let result: Result<()> = async { + // Verify inode exists + let mut stmt = conn + .prepare_cached("SELECT ino FROM fs_inode WHERE ino = ?") + .await?; + let mut rows = stmt.query((ino,)).await?; + + if rows.next().await?.is_none() { + return Err(FsError::NotFound.into()); + } + + // Build the update query dynamically based on which values are provided + let mut updates = Vec::new(); + let mut values: Vec = Vec::new(); + + if let Some(uid) = uid { + updates.push("uid = ?"); + values.push(Value::Integer(uid as i64)); + } + if let Some(gid) = gid { + updates.push("gid = ?"); + values.push(Value::Integer(gid as i64)); + } + + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + let now_secs = dur.as_secs() as i64; + let now_nsec = dur.subsec_nanos() as i64; + updates.push("ctime = ?"); + values.push(Value::Integer(now_secs)); + updates.push("ctime_nsec = ?"); + values.push(Value::Integer(now_nsec)); + + values.push(Value::Integer(ino)); + let sql = format!("UPDATE fs_inode SET {} WHERE ino = ?", updates.join(", ")); + conn.execute(&sql, values).await?; + Ok(()) + } + .await; + + match result { + Ok(()) => { + txn.commit().await?; + self.invalidate_attr(ino); + Ok(()) + } + Err(error) => { + let _ = txn.rollback().await; + Err(error) + } + } + } + + async fn utimens(&self, ino: i64, atime: TimeChange, mtime: TimeChange) -> Result<()> { + if matches!(atime, TimeChange::Omit) && matches!(mtime, TimeChange::Omit) { + return Ok(()); + } + + // Group-commit fast path: with FUSE writeback caching the kernel sends + // one SETATTR (mtime) per freshly written file, usually while that + // file's data is pending in the write batcher (and sometimes after it + // already drained). Instead of paying a dedicated SQLite transaction + // per file for the time UPDATE, stash the resolved values in the + // inode's pending entry (created on demand) — the batcher commits them + // inside its next drain transaction (`apply_pending_times_with_conn`), + // and `merge_pending_view` overlays them onto getattr/lookup results so + // the change is visible immediately. Falls through to the direct + // (transaction-wrapped) UPDATE when overlay reads are disabled or the + // legacy drain is requested. + if !self.core_config.drain_on_setattr && self.overlay_reads { + if let Some(drain) = &self.write_drain { + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + let now = (dur.as_secs() as i64, dur.subsec_nanos() as i64); + let resolve = |tc: TimeChange| -> Option<(i64, i64)> { + match tc { + TimeChange::Set(secs, nsec) => Some((secs, nsec as i64)), + TimeChange::Now => Some(now), + TimeChange::Omit => None, + } + }; + let change = PendingTimeChange { + atime: resolve(atime), + mtime: resolve(mtime), + // utimens always bumps ctime. + ctime: Some(now), + }; + drain.stash_times(ino, change); + self.invalidate_attr(ino); + return Ok(()); + } + } + + self.prepare_attr_change(ino).await?; + let conn = self.pool.get_connection().await?; + // BEGIN IMMEDIATE: see `chmod` — avoid autocommit write/write races + // with concurrent batcher drain transactions. + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let result: Result<()> = async { + // Verify inode exists + let mut stmt = conn + .prepare_cached("SELECT ino FROM fs_inode WHERE ino = ?") + .await?; + let mut rows = stmt.query((ino,)).await?; + if rows.next().await?.is_none() { + return Err(FsError::NotFound.into()); + } + + let mut updates = Vec::new(); + let mut values: Vec = Vec::new(); + + let resolve = |tc: TimeChange| -> (i64, i64) { + match tc { + TimeChange::Set(secs, nsec) => (secs, nsec as i64), + TimeChange::Now => { + let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + (dur.as_secs() as i64, dur.subsec_nanos() as i64) + } + TimeChange::Omit => unreachable!(), + } + }; + + if !matches!(atime, TimeChange::Omit) { + let (secs, nsec) = resolve(atime); + updates.push("atime = ?"); + values.push(Value::Integer(secs)); + updates.push("atime_nsec = ?"); + values.push(Value::Integer(nsec)); + } + + if !matches!(mtime, TimeChange::Omit) { + let (secs, nsec) = resolve(mtime); + updates.push("mtime = ?"); + values.push(Value::Integer(secs)); + updates.push("mtime_nsec = ?"); + values.push(Value::Integer(nsec)); + } + + // Also update ctime + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + updates.push("ctime = ?"); + values.push(Value::Integer(dur.as_secs() as i64)); + updates.push("ctime_nsec = ?"); + values.push(Value::Integer(dur.subsec_nanos() as i64)); + + values.push(Value::Integer(ino)); + let sql = format!("UPDATE fs_inode SET {} WHERE ino = ?", updates.join(", ")); + conn.execute(&sql, values).await?; + Ok(()) + } + .await; + + match result { + Ok(()) => { + txn.commit().await?; + self.invalidate_attr(ino); + Ok(()) + } + Err(error) => { + let _ = txn.rollback().await; + Err(error) + } + } + } + + async fn open(&self, ino: i64, _flags: i32) -> Result { + let conn = self.pool.get_connection().await?; + + // Verify inode exists + let mut stmt = conn + .prepare_cached("SELECT ino FROM fs_inode WHERE ino = ?") + .await?; + let mut rows = stmt.query((ino,)).await?; + + if rows.next().await?.is_none() { + return Err(FsError::NotFound.into()); + } + + Ok(Arc::new(AgentFSFile { + pool: self.pool.clone(), + ino, + chunk_size: self.chunk_size, + inline_threshold: self.inline_threshold, + attr_cache: self.attr_cache.clone(), + pending_view: self.pending_view.clone(), + write_drain: self.write_drain.clone(), + overlay_reads: self.overlay_reads, + _open_guard: Some(self.lifecycle.guard(ino)), + })) + } + + async fn mkdir( + &self, + parent_ino: i64, + name: &str, + mode: u32, + uid: u32, + gid: u32, + ) -> Result { + if name.len() > MAX_NAME_LEN { + return Err(FsError::NameTooLong.into()); + } + let conn = self.pool.get_connection().await?; + // BEGIN IMMEDIATE: see `chmod` — multi-statement metadata mutations + // must not run as autocommit statements that race the write batcher's + // drain transactions (turso reports such write/write races as + // "database snapshot is stale" instead of waiting on the write lock). + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let result: Result = async { + // Check if already exists + if self.lookup_child(&conn, parent_ino, name).await?.is_some() { + return Err(FsError::AlreadyExists.into()); + } + + // Create inode + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + let now_secs = dur.as_secs() as i64; + let now_nsec = dur.subsec_nanos() as i64; + let mut stmt = conn + .prepare_cached( + "INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec) + VALUES (?, ?, ?, 0, ?, ?, ?, ?, ?, ?) RETURNING ino", + ) + .await?; + let dir_mode = super::S_IFDIR | (mode & 0o7777); + let row = stmt + .query_row(( + dir_mode as i64, + uid, + gid, + now_secs, + now_secs, + now_secs, + now_nsec, + now_nsec, + now_nsec, + )) + .await?; + + let ino = row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; + + // Create directory entry + let mut stmt = conn + .prepare_cached("INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)") + .await?; + stmt.execute((name, parent_ino, ino)).await?; + + // Set nlink to 2 for new directory (self "." + parent's dentry) + let mut stmt = conn + .prepare_cached("UPDATE fs_inode SET nlink = 2 WHERE ino = ?") + .await?; + stmt.execute((ino,)).await?; + + // Increment parent nlink (new directory's ".." link) and update timestamps + let mut stmt = conn + .prepare_cached( + "UPDATE fs_inode SET nlink = nlink + 1, ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", + ) + .await?; + stmt.execute((now_secs, now_secs, now_nsec, now_nsec, parent_ino)) + .await?; + + Ok(Stats { + ino, + mode: dir_mode, + nlink: 2, + uid, + gid, + size: 0, + atime: now_secs, + mtime: now_secs, + ctime: now_secs, + atime_nsec: now_nsec as u32, + mtime_nsec: now_nsec as u32, + ctime_nsec: now_nsec as u32, + rdev: 0, + }) + } + .await; + + match result { + Ok(stats) => { + txn.commit().await?; + // Populate dentry cache only after the transaction is durable. + self.cache_dentry(parent_ino, name, stats.ino); + self.invalidate_parent_attr(parent_ino); + self.cache_attr(stats.clone()); + Ok(stats) + } + Err(error) => { + let _ = txn.rollback().await; + Err(error) + } + } + } + + async fn create_file( + &self, + parent_ino: i64, + name: &str, + mode: u32, + uid: u32, + gid: u32, + ) -> Result<(Stats, BoxedFile)> { + if name.len() > MAX_NAME_LEN { + return Err(FsError::NameTooLong.into()); + } + let conn = self.pool.get_connection().await?; + + // No existence pre-check: fs_dentry's UNIQUE(parent_ino, name) makes + // the dentry INSERT below the authoritative collision detector (its + // Constraint error maps to AlreadyExists and the transaction drop + // rolls back the inode row). Saves one SELECT on the synchronous + // create path that every git-clone file pays. + + // Prepare statements before starting the transaction + let mut inode_stmt = conn + .prepare_cached( + "INSERT INTO fs_inode (mode, nlink, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec, data_inline, storage_kind) + VALUES (?, 1, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING ino", + ) + .await?; + let mut dentry_stmt = conn + .prepare_cached("INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)") + .await?; + + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + let now_secs = dur.as_secs() as i64; + let now_nsec = dur.subsec_nanos() as i64; + let file_mode = S_IFREG | (mode & 0o7777); + + let row = inode_stmt + .query_row(( + file_mode as i64, + uid, + gid, + now_secs, + now_secs, + now_secs, + now_nsec, + now_nsec, + now_nsec, + Value::Blob(Vec::new()), + STORAGE_INLINE, + )) + .await?; + + let ino = row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; + + match dentry_stmt.execute((name, parent_ino, ino)).await { + Ok(_) => {} + Err(turso::Error::Constraint(_)) => return Err(FsError::AlreadyExists.into()), + Err(error) => return Err(error.into()), + } + + // Parent mtime/ctime: stash into the batcher overlay (committed by the + // next group drain, served immediately via merge_pending_view) instead + // of paying an UPDATE on the synchronous create path. Falls back to + // the in-transaction UPDATE when the overlay cannot serve reads. + let stash_parent_times = self.overlay_reads && self.write_drain.is_some(); + if !stash_parent_times { + conn.execute( + "UPDATE fs_inode SET ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", + (now_secs, now_secs, now_nsec, now_nsec, parent_ino), + ) + .await?; + } + + txn.commit().await?; + + if stash_parent_times { + if let Some(drain) = &self.write_drain { + drain.stash_times( + parent_ino, + PendingTimeChange { + atime: None, + mtime: Some((now_secs, now_nsec)), + ctime: Some((now_secs, now_nsec)), + }, + ); + } + } + + self.cache_dentry(parent_ino, name, ino); + self.invalidate_parent_attr(parent_ino); + + let stats = Stats { + ino, + mode: file_mode, + nlink: 1, + uid, + gid, + size: 0, + atime: now_secs, + mtime: now_secs, + ctime: now_secs, + atime_nsec: now_nsec as u32, + mtime_nsec: now_nsec as u32, + ctime_nsec: now_nsec as u32, + rdev: 0, + }; + self.cache_attr(stats.clone()); + + let file: BoxedFile = Arc::new(AgentFSFile { + pool: self.pool.clone(), + ino, + chunk_size: self.chunk_size, + inline_threshold: self.inline_threshold, + attr_cache: self.attr_cache.clone(), + pending_view: self.pending_view.clone(), + write_drain: self.write_drain.clone(), + overlay_reads: self.overlay_reads, + _open_guard: Some(self.lifecycle.guard(ino)), + }); + + Ok((stats, file)) + } + + async fn mknod( + &self, + parent_ino: i64, + name: &str, + mode: u32, + rdev: u64, + uid: u32, + gid: u32, + ) -> Result { + if name.len() > MAX_NAME_LEN { + return Err(FsError::NameTooLong.into()); + } + let conn = self.pool.get_connection().await?; + // BEGIN IMMEDIATE: see `mkdir` — never race the batcher's drain + // transactions with autocommit metadata writes. + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let result: Result = async { + // Check if already exists + if self.lookup_child(&conn, parent_ino, name).await?.is_some() { + return Err(FsError::AlreadyExists.into()); + } + + // Create inode with mode and rdev + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + let now_secs = dur.as_secs() as i64; + let now_nsec = dur.subsec_nanos() as i64; + let mut stmt = conn + .prepare_cached( + "INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime, rdev, atime_nsec, mtime_nsec, ctime_nsec) + VALUES (?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?) RETURNING ino", + ) + .await?; + let row = stmt + .query_row(( + mode as i64, + uid, + gid, + now_secs, + now_secs, + now_secs, + rdev as i64, + now_nsec, + now_nsec, + now_nsec, + )) + .await?; + + let ino = row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; + + // Create directory entry + let mut stmt = conn + .prepare_cached("INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)") + .await?; + stmt.execute((name, parent_ino, ino)).await?; + + // Increment link count + let mut stmt = conn + .prepare_cached("UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?") + .await?; + stmt.execute((ino,)).await?; + + // Update parent directory ctime and mtime + let mut stmt = conn + .prepare_cached("UPDATE fs_inode SET ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?") + .await?; + stmt.execute((now_secs, now_secs, now_nsec, now_nsec, parent_ino)) + .await?; + + Ok(Stats { + ino, + mode, + nlink: 1, + uid, + gid, + size: 0, + atime: now_secs, + mtime: now_secs, + ctime: now_secs, + atime_nsec: now_nsec as u32, + mtime_nsec: now_nsec as u32, + ctime_nsec: now_nsec as u32, + rdev, + }) + } + .await; + + match result { + Ok(stats) => { + txn.commit().await?; + // Populate dentry cache only after the transaction is durable. + self.cache_dentry(parent_ino, name, stats.ino); + self.invalidate_parent_attr(parent_ino); + self.cache_attr(stats.clone()); + Ok(stats) + } + Err(error) => { + let _ = txn.rollback().await; + Err(error) + } + } + } + + async fn symlink( + &self, + parent_ino: i64, + name: &str, + target: &str, + uid: u32, + gid: u32, + ) -> Result { + if name.len() > MAX_NAME_LEN { + return Err(FsError::NameTooLong.into()); + } + let conn = self.pool.get_connection().await?; + // BEGIN IMMEDIATE: see `mkdir` — never race the batcher's drain + // transactions with autocommit metadata writes. + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let result: Result = async { + // Check if entry already exists + if self.lookup_child(&conn, parent_ino, name).await?.is_some() { + return Err(FsError::AlreadyExists.into()); + } + + // Create inode for symlink + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + let now_secs = dur.as_secs() as i64; + let now_nsec = dur.subsec_nanos() as i64; + let mode = S_IFLNK | 0o777; // Symlinks typically have 777 permissions + let size = target.len() as i64; + + let mut stmt = conn + .prepare_cached( + "INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING ino", + ) + .await?; + let row = stmt + .query_row(( + mode, uid, gid, size, now_secs, now_secs, now_secs, now_nsec, now_nsec, + now_nsec, + )) + .await?; + + let ino = row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; + + // Store symlink target + conn.execute( + "INSERT INTO fs_symlink (ino, target) VALUES (?, ?)", + (ino, target), + ) + .await?; + + // Create directory entry + conn.execute( + "INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)", + (name, parent_ino, ino), + ) + .await?; + + // Increment link count + conn.execute( + "UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?", + (ino,), + ) + .await?; + + // Update parent directory ctime and mtime + conn.execute( + "UPDATE fs_inode SET ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", + (now_secs, now_secs, now_nsec, now_nsec, parent_ino), + ) + .await?; + + Ok(Stats { + ino, + mode, + nlink: 1, + uid, + gid, + size, + atime: now_secs, + mtime: now_secs, + ctime: now_secs, + atime_nsec: now_nsec as u32, + mtime_nsec: now_nsec as u32, + ctime_nsec: now_nsec as u32, + rdev: 0, + }) + } + .await; + + match result { + Ok(stats) => { + txn.commit().await?; + // Populate dentry cache only after the transaction is durable. + self.cache_dentry(parent_ino, name, stats.ino); + self.invalidate_parent_attr(parent_ino); + self.cache_attr(stats.clone()); + Ok(stats) + } + Err(error) => { + let _ = txn.rollback().await; + Err(error) + } + } + } + + async fn unlink(&self, parent_ino: i64, name: &str) -> Result<()> { + if name.len() > MAX_NAME_LEN { + return Err(FsError::NameTooLong.into()); + } + self.process_deferred_reaps().await?; + let conn = self.pool.get_connection().await?; + // BEGIN IMMEDIATE: this is the path that intermittently failed with + // "database snapshot is stale" -> EIO when its autocommit statements + // raced the write batcher's drain transactions (git unlinking + // `.git/config.lock` during a clone). The transaction also makes the + // dentry/nlink/inode removal atomic. + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let result: Result<(i64, Option)> = async { + // Look up the child inode + let ino = self + .lookup_child(&conn, parent_ino, name) + .await? + .ok_or(FsError::NotFound)?; + + // Check if it's a directory (use rmdir for directories) + if let Some(mode) = store::mode(&conn, ino).await? { + if (mode & S_IFMT) == super::S_IFDIR { + return Err(FsError::IsADirectory.into()); + } + } else { + return Err(FsError::NotFound.into()); + } + + // Delete the directory entry + let mut stmt = conn + .prepare_cached("DELETE FROM fs_dentry WHERE parent_ino = ? AND name = ?") + .await?; + stmt.execute((parent_ino, name)).await?; + + // Update parent directory mtime and ctime + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + let now_secs = dur.as_secs() as i64; + let now_nsec = dur.subsec_nanos() as i64; + let mut stmt = conn + .prepare_cached("UPDATE fs_inode SET mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?") + .await?; + stmt.execute((now_secs, now_secs, now_nsec, now_nsec, parent_ino)) + .await?; + + // Decrement link count and update ctime + let mut stmt = conn + .prepare_cached( + "UPDATE fs_inode SET nlink = nlink - 1, ctime = ?, ctime_nsec = ? WHERE ino = ?", + ) + .await?; + stmt.execute((now_secs, now_nsec, ino)).await?; + + // Check if this was the last link to the inode. POSIX: while + // open handles exist the nlink=0 rows stay alive; the last + // handle drop queues the orphan for process_deferred_reaps. + let link_count = self.get_link_count(&conn, ino).await?; + let removed = link_count == 0 && !self.lifecycle.defer_reap_if_open(ino); + let reaped_ino = if removed && self.reap_inode_with_conn(&conn, ino).await? { + Some(ino) + } else { + None + }; + + Ok((ino, reaped_ino)) + } + .await; + + match result { + Ok((ino, reaped_ino)) => { + txn.commit().await?; + if let Some(reaped_ino) = reaped_ino { + self.discard_pending_for_reaped_inode(reaped_ino); + } + self.invalidate_dentry(parent_ino, name); + self.invalidate_parent_attr(parent_ino); + self.invalidate_attr(ino); + self.cache_negative_dentry(parent_ino, name); + Ok(()) + } + Err(error) => { + let _ = txn.rollback().await; + Err(error) + } + } + } + + async fn rmdir(&self, parent_ino: i64, name: &str) -> Result<()> { + if name.len() > MAX_NAME_LEN { + return Err(FsError::NameTooLong.into()); + } + self.process_deferred_reaps().await?; + let conn = self.pool.get_connection().await?; + // BEGIN IMMEDIATE: see `unlink` — never race the batcher's drain + // transactions with autocommit metadata writes. + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let result: Result = async { + // Look up the child inode + let ino = self + .lookup_child(&conn, parent_ino, name) + .await? + .ok_or(FsError::NotFound)?; + + if ino == ROOT_INO { + return Err(FsError::RootOperation.into()); + } + + // Check if it's a directory + if let Some(mode) = store::mode(&conn, ino).await? { + if (mode & S_IFMT) != super::S_IFDIR { + return Err(FsError::NotADirectory.into()); + } + } else { + return Err(FsError::NotFound.into()); + } + + // Check if directory is empty + let mut stmt = conn + .prepare_cached("SELECT COUNT(*) FROM fs_dentry WHERE parent_ino = ?") + .await?; + let mut rows = stmt.query((ino,)).await?; + + if let Some(row) = rows.next().await? { + let count = row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(0); + if count > 0 { + return Err(FsError::NotEmpty.into()); + } + } + + // Delete the directory entry + let mut stmt = conn + .prepare_cached("DELETE FROM fs_dentry WHERE parent_ino = ? AND name = ?") + .await?; + stmt.execute((parent_ino, name)).await?; + + // Decrement link count on removed directory + let mut stmt = conn + .prepare_cached("UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?") + .await?; + stmt.execute((ino,)).await?; + + // Decrement parent nlink (removed directory's ".." link) and update timestamps + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + let now_secs = dur.as_secs() as i64; + let now_nsec = dur.subsec_nanos() as i64; + let mut stmt = conn + .prepare_cached( + "UPDATE fs_inode SET nlink = nlink - 1, ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", + ) + .await?; + stmt.execute((now_secs, now_secs, now_nsec, now_nsec, parent_ino)) + .await?; + + // Delete inode if no more links + let link_count = self.get_link_count(&conn, ino).await?; + if link_count == 0 { + let mut stmt = conn + .prepare_cached("DELETE FROM fs_inode WHERE ino = ?") + .await?; + stmt.execute((ino,)).await?; + } + + Ok(ino) + } + .await; + + match result { + Ok(ino) => { + txn.commit().await?; + self.invalidate_dentry(parent_ino, name); + self.invalidate_parent_attr(parent_ino); + self.invalidate_attr(ino); + self.cache_negative_dentry(parent_ino, name); + Ok(()) + } + Err(error) => { + let _ = txn.rollback().await; + Err(error) + } + } + } + + async fn link(&self, ino: i64, newparent_ino: i64, newname: &str) -> Result { + if newname.len() > MAX_NAME_LEN { + return Err(FsError::NameTooLong.into()); + } + let conn = self.pool.get_connection().await?; + // BEGIN IMMEDIATE: see `unlink` — never race the batcher's drain + // transactions with autocommit metadata writes. + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let result: Result = async { + // Check if source inode exists and is not a directory + if let Some(mode) = store::mode(&conn, ino).await? { + if (mode & S_IFMT) == super::S_IFDIR { + return Err(FsError::IsADirectory.into()); + } + } else { + return Err(FsError::NotFound.into()); + } + + // Check if destination already exists + if self + .lookup_child(&conn, newparent_ino, newname) + .await? + .is_some() + { + return Err(FsError::AlreadyExists.into()); + } + + // Create directory entry pointing to the same inode + conn.execute( + "INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)", + (newname, newparent_ino, ino), + ) + .await?; + + // Increment link count and update ctime + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + let now_secs = dur.as_secs() as i64; + let now_nsec = dur.subsec_nanos() as i64; + conn.execute( + "UPDATE fs_inode SET nlink = nlink + 1, ctime = ?, ctime_nsec = ? WHERE ino = ?", + (now_secs, now_nsec, ino), + ) + .await?; + + // Update parent directory ctime and mtime + conn.execute( + "UPDATE fs_inode SET ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", + (now_secs, now_secs, now_nsec, now_nsec, newparent_ino), + ) + .await?; + + // Return updated stats (drop the cached pre-link attr so the read + // below reflects the nlink/ctime updates made in this transaction). + self.invalidate_attr(ino); + self.getattr_with_conn(&conn, ino) + .await? + .ok_or(FsError::NotFound.into()) + } + .await; + + match result { + Ok(stats) => { + txn.commit().await?; + // Populate dentry cache only after the transaction is durable. + self.cache_dentry(newparent_ino, newname, ino); + self.invalidate_parent_attr(newparent_ino); + self.invalidate_attr(ino); + Ok(stats) + } + Err(error) => { + let _ = txn.rollback().await; + self.invalidate_attr(ino); + Err(error) + } + } + } + + async fn rename( + &self, + oldparent_ino: i64, + oldname: &str, + newparent_ino: i64, + newname: &str, + ) -> Result<()> { + self.rename_with_replaced_ino(oldparent_ino, oldname, newparent_ino, newname) + .await + .map(|_| ()) + } + + async fn rename_with_replaced_ino( + &self, + oldparent_ino: i64, + oldname: &str, + newparent_ino: i64, + newname: &str, + ) -> Result> { + if newname.len() > MAX_NAME_LEN { + return Err(FsError::NameTooLong.into()); + } + self.process_deferred_reaps().await?; + let conn = self.pool.get_connection().await?; + + // Get source inode + let src_ino = self + .lookup_child(&conn, oldparent_ino, oldname) + .await? + .ok_or(FsError::NotFound)?; + + if src_ino == ROOT_INO { + return Err(FsError::RootOperation.into()); + } + + // Get source stats to check if it's a directory + let src_stats = self + .getattr_with_conn(&conn, src_ino) + .await? + .ok_or(FsError::NotFound)?; + + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + + let result: Result<(Option, Option)> = async { + let mut replaced_dst_ino = None; + let mut reaped_dst_ino = None; + + if src_stats.is_directory() { + let mut ancestor_ino = newparent_ino; + let mut visited = HashSet::new(); + while ancestor_ino != ROOT_INO { + if ancestor_ino == src_ino { + return Err(FsError::InvalidRename.into()); + } + if !visited.insert(ancestor_ino) { + return Err(FsError::InvalidPath.into()); + } + + let mut stmt = conn + .prepare_cached("SELECT parent_ino FROM fs_dentry WHERE ino = ?") + .await?; + let mut rows = stmt.query((ancestor_ino,)).await?; + let parent_ino = rows + .next() + .await? + .ok_or(FsError::NotFound)? + .get_value(0) + .ok() + .and_then(|value| value.as_integer().copied()) + .ok_or(FsError::InvalidPath)?; + if rows.next().await?.is_some() { + return Err(FsError::InvalidPath.into()); + } + ancestor_ino = parent_ino; + } + } + + // Check if destination exists + if let Some(dst_ino) = self.lookup_child(&conn, newparent_ino, newname).await? { + replaced_dst_ino = Some(dst_ino); + let dst_stats = self.getattr_with_conn(&conn, dst_ino).await?.ok_or(FsError::NotFound)?; + + // Can't replace directory with non-directory + if dst_stats.is_directory() && !src_stats.is_directory() { + return Err(FsError::IsADirectory.into()); + } + + // Can't replace non-directory with directory + if !dst_stats.is_directory() && src_stats.is_directory() { + return Err(FsError::NotADirectory.into()); + } + + // If destination is directory, it must be empty + if dst_stats.is_directory() { + let mut stmt = conn + .prepare_cached("SELECT COUNT(*) FROM fs_dentry WHERE parent_ino = ?") + .await?; + let mut rows = stmt.query((dst_ino,)).await?; + + if let Some(row) = rows.next().await? { + let count = row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(0); + if count > 0 { + return Err(FsError::NotEmpty.into()); + } + } + } + + // Remove destination entry + let mut stmt = conn + .prepare_cached("DELETE FROM fs_dentry WHERE parent_ino = ? AND name = ?") + .await?; + stmt.execute((newparent_ino, newname)).await?; + + // Decrement link count and update ctime on destination inode + let dur_dec = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default(); + let now_dec = dur_dec.as_secs() as i64; + let now_dec_nsec = dur_dec.subsec_nanos() as i64; + let mut stmt = conn + .prepare_cached("UPDATE fs_inode SET nlink = nlink - 1, ctime = ?, ctime_nsec = ? WHERE ino = ?") + .await?; + stmt.execute((now_dec, now_dec_nsec, dst_ino)).await?; + + // Clean up destination inode if no more links (deferred while + // open handles exist — see lifecycle). + let link_count = self.get_link_count(&conn, dst_ino).await?; + if link_count == 0 + && !self.lifecycle.defer_reap_if_open(dst_ino) + && self.reap_inode_with_conn(&conn, dst_ino).await? + { + reaped_dst_ino = Some(dst_ino); + } + } + + // Update the dentry: change parent and/or name + let mut stmt = conn + .prepare_cached( + "UPDATE fs_dentry SET parent_ino = ?, name = ? WHERE parent_ino = ? AND name = ?", + ) + .await?; + stmt.execute((newparent_ino, newname, oldparent_ino, oldname)) + .await?; + + // If renaming a directory across parents, adjust parent nlink counts + // (the ".." link moves from old parent to new parent) + if src_stats.is_directory() && oldparent_ino != newparent_ino { + let mut stmt = conn + .prepare_cached("UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?") + .await?; + stmt.execute((oldparent_ino,)).await?; + + let mut stmt = conn + .prepare_cached("UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?") + .await?; + stmt.execute((newparent_ino,)).await?; + } + + // Update ctime of the inode + let dur = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default(); + let now_secs = dur.as_secs() as i64; + let now_nsec = dur.subsec_nanos() as i64; + + let mut stmt = conn + .prepare_cached("UPDATE fs_inode SET ctime = ?, ctime_nsec = ? WHERE ino = ?") + .await?; + stmt.execute((now_secs, now_nsec, src_ino)).await?; + + // Update source parent directory timestamps + let mut stmt = conn + .prepare_cached("UPDATE fs_inode SET mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?") + .await?; + stmt.execute((now_secs, now_secs, now_nsec, now_nsec, oldparent_ino)).await?; + + // Update destination parent directory timestamps + if newparent_ino != oldparent_ino { + let mut stmt = conn + .prepare_cached("UPDATE fs_inode SET mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?") + .await?; + stmt.execute((now_secs, now_secs, now_nsec, now_nsec, newparent_ino)).await?; + } + + Ok((replaced_dst_ino, reaped_dst_ino)) + } + .await; + + match result { + Ok((replaced_dst_ino, reaped_dst_ino)) => { + txn.commit().await?; + if let Some(reaped_dst_ino) = reaped_dst_ino { + self.discard_pending_for_reaped_inode(reaped_dst_ino); + } + + // Invalidate cache for source and destination + self.invalidate_dentry(oldparent_ino, oldname); + self.invalidate_dentry(newparent_ino, newname); + self.invalidate_attr(src_ino); + self.invalidate_parent_attr(oldparent_ino); + self.invalidate_parent_attr(newparent_ino); + if let Some(dst_ino) = replaced_dst_ino { + self.invalidate_attr(dst_ino); + } + + // Add exact post-rename namespace state to the caches. + if oldparent_ino != newparent_ino || oldname != newname { + self.cache_negative_dentry(oldparent_ino, oldname); + } + self.cache_dentry(newparent_ino, newname, src_ino); + + Ok(replaced_dst_ino) + } + Err(e) => { + let _ = txn.rollback().await; + Err(e) + } + } + } + + async fn statfs(&self) -> Result { + AgentFS::statfs(self).await + } + + async fn drain_inode_writes(&self, ino: i64) -> Result<()> { + AgentFS::drain_inode_writes(self, ino).await + } + + async fn drain_all(&self) -> Result<()> { + AgentFS::drain_all(self).await + } + + async fn finalize(&self) -> Result<()> { + AgentFS::finalize(self).await + } + + fn register_reap_hook(&self, hook: Arc) -> bool { + AgentFS::register_reap_hook(self, hook) + } + + // `forget` deliberately uses the default no-op trait impl: a FORGET only + // drops the kernel's reference to the inode. Pending batched writes stay + // readable through the Tier-4 overlay and are committed by the batcher + // timer/bytes triggers, fsync, or finalize — committing them here issued + // one serial SQLite transaction per written file during clone workloads + // (the kernel FORGETs each file shortly after our post-write entry + // invalidation). +} diff --git a/crates/agentfs-core/src/fs/agentfs/import.rs b/crates/agentfs-core/src/fs/agentfs/import.rs new file mode 100644 index 00000000..c9cdc0fb --- /dev/null +++ b/crates/agentfs-core/src/fs/agentfs/import.rs @@ -0,0 +1,338 @@ +//! Bulk import session for AgentFS clone and import flows. +//! +//! Import keeps one pooled connection and a directory inode map across chunks, +//! committing entries in bounded transactions sized by the batcher config. +//! The transaction chunking defaults are clone-performance-sensitive. + +use std::collections::HashMap; + +use turso::transaction::{Transaction, TransactionBehavior}; +use turso::Value; + +use crate::error::Error; + +use super::*; + +/// One node for [`AgentFS::import_entries`]. `path` is relative to the import +/// root and '/'-separated; parent directories must precede their children. +#[derive(Debug, Clone)] +pub struct ImportEntry { + pub path: String, + /// Full `st_mode` bits (S_IFDIR / S_IFREG / S_IFLNK plus permissions). + pub mode: u32, + /// File content, or the symlink target bytes; empty for directories. + pub data: Vec, +} + +/// Result row for one imported node: echoes the exact `ino`/`mode`/`size` +/// the filesystem will serve so callers can fabricate externally-consistent +/// stat metadata (e.g. a git index) without re-reading content. +#[derive(Debug, Clone)] +pub struct ImportedEntry { + pub path: String, + pub ino: i64, + pub mode: u32, + pub size: u64, +} + +/// Ownership and timestamps applied to every node of one bulk import. +#[derive(Debug, Clone)] +pub struct ImportOptions { + pub uid: u32, + pub gid: u32, + /// (secs, nanos) stamped as atime/mtime/ctime on every imported inode. + pub timestamp: (i64, i64), +} + +/// A streaming bulk import started by [`AgentFS::begin_import`]. Holds one +/// pooled connection plus the directory-path -> ino map across +/// [`ImportSession::import_chunk`] calls, so a producer can feed entries as +/// they become available (e.g. as `git cat-file --batch` emits blobs) +/// instead of buffering the whole tree in memory. The ordering contract +/// matches [`AgentFS::import_entries`]: every parent directory must appear +/// in some chunk before (or in the same chunk as) its children. +pub struct ImportSession { + fs: AgentFS, + conn: crate::pool::PooledConnection, + dest_parent: i64, + opts: ImportOptions, + dir_inos: HashMap, + results: Vec, +} + +impl ImportSession { + /// Import one batch of entries. Parent directories imported by earlier + /// chunks (or earlier in this chunk) resolve normally; a parent that has + /// never been imported yields `FsError::NotFound`. + pub async fn import_chunk(&mut self, entries: &[ImportEntry]) -> Result<()> { + self.fs + .import_chunk_with_conn( + &self.conn, + self.dest_parent, + &self.opts, + &mut self.dir_inos, + &mut self.results, + entries, + ) + .await + } + + /// Finish the import and return one [`ImportedEntry`] per imported node, + /// in the order the entries were fed. + pub fn finish(self) -> Vec { + self.fs.invalidate_attr(self.dest_parent); + self.results + } +} + +impl AgentFS { + /// Bulk-import a tree of nodes under `dest_parent` using large + /// multi-inode transactions instead of one transaction per node, sized by + /// the write batcher's txn limits (`AGENTFS_BATCH_TXN_INODES` / + /// `AGENTFS_BATCH_TXN_BYTES`). This is the fast path for populating the + /// database without per-file FUSE round trips (`agentfs clone` / `fs + /// import`): a 4.7k-file worktree pays a handful of commits instead of + /// ~9.4k per-file create+write transaction boundaries. + /// + /// Entries must be ordered parents-before-children; every parent + /// directory of a nested path must itself appear as an entry (or be the + /// import root). All inodes are stamped with `opts.timestamp`, and the + /// returned rows echo the exact `ino`/`mode`/`size` the filesystem will + /// serve, so callers can fabricate externally-consistent stat metadata + /// (e.g. a git index) without re-reading anything. + pub async fn import_entries( + &self, + dest_parent: i64, + entries: &[ImportEntry], + opts: &ImportOptions, + ) -> Result> { + let mut session = self.begin_import(dest_parent, opts.clone()).await?; + session.import_chunk(entries).await?; + Ok(session.finish()) + } + + /// Begin a streaming bulk import under `dest_parent`; see + /// [`ImportSession`]. [`AgentFS::import_entries`] is the buffered + /// one-shot form. + pub async fn begin_import( + &self, + dest_parent: i64, + opts: ImportOptions, + ) -> Result { + Ok(ImportSession { + fs: self.clone(), + conn: self.pool.get_connection().await?, + dest_parent, + opts, + dir_inos: HashMap::new(), + results: Vec::new(), + }) + } + + /// One chunk of a streaming import. `conn`, `dir_inos`, and `results` + /// persist across calls so later chunks may reference directories + /// imported by earlier ones; each call still splits its entries into + /// bounded transactions. + async fn import_chunk_with_conn( + &self, + conn: &crate::pool::PooledConnection, + dest_parent: i64, + opts: &ImportOptions, + dir_inos: &mut HashMap, + results: &mut Vec, + entries: &[ImportEntry], + ) -> Result<()> { + let max_inodes = self.core_config.batcher.txn_max_inodes.max(1); + let max_bytes = self.core_config.batcher.txn_max_bytes.max(1); + let (ts_secs, ts_nsec) = opts.timestamp; + + let mut inode_stmt = conn + .prepare_cached( + "INSERT INTO fs_inode (mode, nlink, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec, data_inline, storage_kind) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING ino", + ) + .await?; + let mut dentry_stmt = conn + .prepare_cached("INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)") + .await?; + let mut chunk_stmt = conn + .prepare_cached("INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?)") + .await?; + let mut symlink_stmt = conn + .prepare_cached("INSERT INTO fs_symlink (ino, target) VALUES (?, ?)") + .await?; + let mut parent_stmt = conn + .prepare_cached( + "UPDATE fs_inode SET nlink = nlink + ?, ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", + ) + .await?; + + results.reserve(entries.len()); + + let mut idx = 0usize; + while idx < entries.len() { + let mut batch_end = idx; + let mut batch_bytes = 0usize; + while batch_end < entries.len() + && batch_end - idx < max_inodes + && (batch_end == idx || batch_bytes + entries[batch_end].data.len() <= max_bytes) + { + batch_bytes += entries[batch_end].data.len(); + batch_end += 1; + } + + // Cache fills staged until after a successful commit so a rolled + // back batch never leaves phantom dentries/attrs behind. + let mut staged: Vec<(i64, String, Stats)> = Vec::with_capacity(batch_end - idx); + // parent ino -> nlink bump from new subdirectories (".."). + let mut parent_bumps: HashMap = HashMap::new(); + + let txn = Transaction::new_unchecked(conn, TransactionBehavior::Immediate).await?; + for entry in &entries[idx..batch_end] { + let (parent_path, name) = match entry.path.rsplit_once('/') { + Some((parent, name)) => (parent, name), + None => ("", entry.path.as_str()), + }; + if name.is_empty() || name == "." || name == ".." { + return Err(FsError::InvalidPath.into()); + } + if name.len() > MAX_NAME_LEN { + return Err(FsError::NameTooLong.into()); + } + let parent_ino = if parent_path.is_empty() { + dest_parent + } else { + *dir_inos + .get(parent_path) + .ok_or_else(|| Error::Fs(FsError::NotFound))? + }; + + let kind = entry.mode & S_IFMT; + let (nlink, size, data_inline, storage_kind) = match kind { + S_IFDIR => (2i64, 0u64, Value::Null, STORAGE_CHUNKED), + S_IFLNK => (1, entry.data.len() as u64, Value::Null, STORAGE_CHUNKED), + S_IFREG => { + if entry.data.len() <= self.inline_threshold { + ( + 1, + entry.data.len() as u64, + Value::Blob(entry.data.clone()), + STORAGE_INLINE, + ) + } else { + (1, entry.data.len() as u64, Value::Null, STORAGE_CHUNKED) + } + } + _ => return Err(FsError::InvalidPath.into()), + }; + + let row = inode_stmt + .query_row(( + entry.mode as i64, + nlink, + opts.uid, + opts.gid, + size as i64, + ts_secs, + ts_secs, + ts_secs, + ts_nsec, + ts_nsec, + ts_nsec, + data_inline, + storage_kind, + )) + .await?; + let ino = row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; + + match dentry_stmt.execute((name, parent_ino, ino)).await { + Ok(_) => {} + Err(turso::Error::Constraint(_)) => return Err(FsError::AlreadyExists.into()), + Err(error) => return Err(error.into()), + } + + match kind { + S_IFDIR => { + dir_inos.insert(entry.path.clone(), ino); + *parent_bumps.entry(parent_ino).or_insert(0) += 1; + } + S_IFLNK => { + let target = std::str::from_utf8(&entry.data) + .map_err(|_| Error::Fs(FsError::InvalidPath))?; + symlink_stmt.execute((ino, target)).await?; + parent_bumps.entry(parent_ino).or_insert(0); + } + _ => { + if storage_kind == STORAGE_CHUNKED { + for (chunk_index, chunk) in + entry.data.chunks(self.chunk_size).enumerate() + { + chunk_stmt + .execute((ino, chunk_index as i64, Value::Blob(chunk.to_vec()))) + .await?; + } + } + parent_bumps.entry(parent_ino).or_insert(0); + } + } + + staged.push(( + parent_ino, + name.to_string(), + Stats { + ino, + mode: entry.mode, + nlink: nlink as u32, + uid: opts.uid, + gid: opts.gid, + size: size as i64, + atime: ts_secs, + mtime: ts_secs, + ctime: ts_secs, + atime_nsec: ts_nsec as u32, + mtime_nsec: ts_nsec as u32, + ctime_nsec: ts_nsec as u32, + rdev: 0, + }, + )); + results.push(ImportedEntry { + path: entry.path.clone(), + ino, + mode: entry.mode, + size, + }); + } + + for (parent_ino, bump) in &parent_bumps { + parent_stmt + .execute((*bump, ts_secs, ts_secs, ts_nsec, ts_nsec, *parent_ino)) + .await?; + } + + txn.commit().await?; + #[cfg(test)] + self.import_commit_sizes.lock().unwrap().push(staged.len()); + crate::telemetry::record_agentfs_batcher_commit_txn(staged.len() as u64); + + for (parent_ino, name, stats) in staged { + self.cache_dentry(parent_ino, &name, stats.ino); + // Directories keep changing (nlink/time bumps as later batches + // add children), so only leaf attrs are safe to prime. + if stats.mode & S_IFMT != S_IFDIR { + self.cache_attr(stats); + } + } + for parent_ino in parent_bumps.keys() { + self.invalidate_attr(*parent_ino); + } + + idx = batch_end; + } + + Ok(()) + } +} diff --git a/crates/agentfs-core/src/fs/agentfs/lifecycle.rs b/crates/agentfs-core/src/fs/agentfs/lifecycle.rs new file mode 100644 index 00000000..0ce60284 --- /dev/null +++ b/crates/agentfs-core/src/fs/agentfs/lifecycle.rs @@ -0,0 +1,252 @@ +use crate::error::Result; +use crate::pool::ConnectionPool; +use async_trait::async_trait; +use std::collections::{HashMap, HashSet}; +use std::sync::{Arc, Mutex}; +use turso::transaction::{Transaction, TransactionBehavior}; +use turso::Connection; + +/// Hook invoked in the same SQLite transaction that reaps an inode. +/// +/// Implementors may delete sidecar rows keyed by `ino`; returning an error +/// aborts and rolls back the whole reap transaction, including core row +/// deletion. +#[async_trait] +pub trait ReapHook: Send + Sync { + fn dedup_key(&self) -> Option<&'static str> { + None + } + + async fn on_reap(&self, conn: &Connection, ino: i64) -> Result<()>; +} + +/// Owns open-inode tracking, deferred orphan queues, and reap hooks. +#[derive(Default)] +pub(crate) struct Lifecycle { + open_inodes: Arc, + reap_hooks: Mutex>>, +} + +impl Lifecycle { + pub(crate) fn guard(&self, ino: i64) -> OpenInodeGuard { + self.open_inodes.guard(ino) + } + + /// Marks the inode for deferred reaping when handles are live. + /// Returns true when the caller must NOT delete the rows yet. + pub(crate) fn defer_reap_if_open(&self, ino: i64) -> bool { + self.open_inodes.defer_reap_if_open(ino) + } + + pub fn register_reap_hook(&self, hook: Arc) -> bool { + let mut hooks = self.reap_hooks.lock().unwrap(); + if let Some(key) = hook.dedup_key() { + if hooks + .iter() + .any(|registered| registered.dedup_key() == Some(key)) + { + return false; + } + } + hooks.push(hook); + true + } + + /// Sweep POSIX orphans a crash stranded: nlink = 0 rows are files that + /// were unlinked while open and never queued for deferred reap because the + /// process died. They are invisible (no dentry), so deleting them before + /// serving is safe. + pub(crate) async fn sweep_mount_orphans(&self, conn: &Connection) -> Result> { + let inos = self.nlink_zero_inos(conn).await?; + if inos.is_empty() { + return Ok(Vec::new()); + } + + let txn = Transaction::new_unchecked(conn, TransactionBehavior::Immediate).await?; + let result: Result> = async { + let mut reaped = Vec::new(); + for ino in &inos { + if self.reap_inode_with_conn(conn, *ino).await? { + reaped.push(*ino); + } + } + Ok(reaped) + } + .await; + + match result { + Ok(reaped) => { + txn.commit().await?; + Ok(reaped) + } + Err(error) => { + let _ = txn.rollback().await; + Err(error) + } + } + } + + /// Reap inodes whose deletion unlink/rename deferred because open + /// handles existed (POSIX unlink-while-open). Runs opportunistically at + /// namespace mutations and at finalize; crash recovery is covered by the + /// mount sweep. + pub(crate) async fn process_deferred_reaps( + &self, + pool: &ConnectionPool, + before_reap: F, + ) -> Result> + where + F: Fn(i64), + { + if !self.open_inodes.has_pending_reaps() { + return Ok(Vec::new()); + } + let inos = self.open_inodes.take_reap_queue(); + for ino in &inos { + before_reap(*ino); + } + let conn = pool.get_connection().await?; + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let result: Result> = async { + let mut reaped = Vec::new(); + for ino in &inos { + if self.reap_inode_with_conn(&conn, *ino).await? { + reaped.push(*ino); + } + } + Ok(reaped) + } + .await; + + match result { + Ok(reaped) => { + txn.commit().await?; + Ok(reaped) + } + Err(error) => { + let _ = txn.rollback().await; + self.open_inodes.requeue_reaps(inos); + Err(error) + } + } + } + + /// Delete an already-unlinked inode and its core storage rows using the + /// caller's transaction. The nlink=0 guard makes stale queue entries a + /// no-op. + pub(crate) async fn reap_inode_with_conn(&self, conn: &Connection, ino: i64) -> Result { + let changed = conn + .execute("DELETE FROM fs_inode WHERE ino = ? AND nlink = 0", (ino,)) + .await?; + if changed == 0 { + return Ok(false); + } + + for hook in self.hooks_snapshot() { + hook.on_reap(conn, ino).await?; + } + conn.execute("DELETE FROM fs_data WHERE ino = ?", (ino,)) + .await?; + conn.execute("DELETE FROM fs_symlink WHERE ino = ?", (ino,)) + .await?; + Ok(true) + } + + fn hooks_snapshot(&self) -> Vec> { + self.reap_hooks.lock().unwrap().iter().cloned().collect() + } + + #[cfg(test)] + pub(crate) fn reap_hook_count(&self) -> usize { + self.reap_hooks.lock().unwrap().len() + } + + async fn nlink_zero_inos(&self, conn: &Connection) -> Result> { + let mut rows = conn + .query("SELECT ino FROM fs_inode WHERE nlink = 0", ()) + .await?; + let mut inos = Vec::new(); + while let Some(row) = rows.next().await? { + let ino: i64 = row.get(0)?; + inos.push(ino); + } + Ok(inos) + } +} + +/// Tracks inodes with live `AgentFSFile` handles so unlink and +/// rename-replace can defer row deletion: POSIX requires an +/// unlinked-but-open file to stay readable and writable until its last +/// handle closes. +#[derive(Default)] +struct OpenInodes { + inner: Mutex, +} + +#[derive(Default)] +struct OpenInodesInner { + counts: HashMap, + orphaned: HashSet, + reap_queue: Vec, +} + +impl OpenInodes { + fn guard(self: &Arc, ino: i64) -> OpenInodeGuard { + let mut inner = self.inner.lock().unwrap(); + *inner.counts.entry(ino).or_insert(0) += 1; + OpenInodeGuard { + registry: Arc::clone(self), + ino, + } + } + + fn defer_reap_if_open(&self, ino: i64) -> bool { + let mut inner = self.inner.lock().unwrap(); + if inner.counts.contains_key(&ino) { + inner.orphaned.insert(ino); + true + } else { + false + } + } + + fn release(&self, ino: i64) { + let mut inner = self.inner.lock().unwrap(); + match inner.counts.get_mut(&ino) { + Some(count) if *count > 1 => *count -= 1, + Some(_) => { + inner.counts.remove(&ino); + if inner.orphaned.remove(&ino) { + inner.reap_queue.push(ino); + } + } + None => {} + } + } + + fn take_reap_queue(&self) -> Vec { + let mut inner = self.inner.lock().unwrap(); + std::mem::take(&mut inner.reap_queue) + } + + fn requeue_reaps(&self, inos: Vec) { + let mut inner = self.inner.lock().unwrap(); + inner.reap_queue.extend(inos); + } + + fn has_pending_reaps(&self) -> bool { + !self.inner.lock().unwrap().reap_queue.is_empty() + } +} + +/// RAII registration of one `AgentFSFile` in [`OpenInodes`]. +pub(crate) struct OpenInodeGuard { + registry: Arc, + ino: i64, +} + +impl Drop for OpenInodeGuard { + fn drop(&mut self) { + self.registry.release(self.ino); + } +} diff --git a/crates/agentfs-core/src/fs/agentfs/mod.rs b/crates/agentfs-core/src/fs/agentfs/mod.rs new file mode 100644 index 00000000..5b50eda1 --- /dev/null +++ b/crates/agentfs-core/src/fs/agentfs/mod.rs @@ -0,0 +1,963 @@ +//! AgentFS core facade and module spine. +//! +//! This module owns the shared `AgentFS` state, connection setup, path +//! resolution helpers, cache invalidation hooks, and lifecycle spine. Focused +//! child modules implement caches, file handles, bulk import, path delegates, +//! and the canonical `FileSystem` trait implementation. + +#[cfg(test)] +use crate::error::Error; +use crate::error::Result; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +#[cfg(test)] +use std::sync::Mutex; +use std::time::{SystemTime, UNIX_EPOCH}; +use turso::{Builder, Connection, Value}; + +use super::{ + BoxedFile, FilesystemStats, FsError, Stats, DEFAULT_DIR_MODE, MAX_NAME_LEN, S_IFDIR, S_IFLNK, + S_IFMT, S_IFREG, +}; +#[cfg(test)] +use super::{FileSystem, TimeChange, WriteRange, DEFAULT_FILE_MODE}; +#[cfg(test)] +use crate::config::BatcherConfig; +use crate::config::{CoreConfig, Geometry, DEFAULT_CHUNK_SIZE, DEFAULT_INLINE_THRESHOLD}; +use crate::pool::{ConnectionPool, DatabaseType, PoolOptions}; +use crate::schema; + +mod batcher; +mod caches; +mod file; +mod fs; +mod import; +mod lifecycle; +mod path_api; +pub(in crate::fs) mod store; + +use batcher::{ + AgentFSWriteBatcher, BatcherDrain, BatcherPendingView, Drain, PendingGeneration, PendingView, +}; +use caches::{AttrCache, DentryCache, NegativeDentryCache}; +pub use file::AgentFSFile; +pub use import::{ImportEntry, ImportOptions, ImportSession, ImportedEntry}; +pub use lifecycle::ReapHook; +use lifecycle::{Lifecycle, OpenInodeGuard}; + +#[cfg(test)] +use store::{ + dense_after_inline_write_batch, normalize_write_ranges, NormalizedWriteRange, WriteRangeRef, +}; + +const ROOT_INO: i64 = 1; +const STORAGE_CHUNKED: i64 = 0; +const STORAGE_INLINE: i64 = 1; +const DENTRY_CACHE_MAX_SIZE: usize = 10000; +const NEGATIVE_DENTRY_CACHE_MAX_SIZE: usize = 10000; +const FILE_BACKED_MAX_CONNECTIONS: usize = 8; +const TEMP_STORE_MEMORY_SQL: &str = "PRAGMA temp_store = MEMORY"; +const BUSY_TIMEOUT_SQL: &str = "PRAGMA busy_timeout = 5000"; +const WAL_MODE_SQL: &str = "PRAGMA journal_mode = WAL"; +const BASELINE_SYNCHRONOUS_SQL: &str = "PRAGMA synchronous = NORMAL"; +const DURABLE_SYNCHRONOUS_SQL: &str = "PRAGMA synchronous = FULL"; +const WAL_CHECKPOINT_SQL: &str = "PRAGMA wal_checkpoint(TRUNCATE)"; +const FILE_BACKED_SETUP_SQL: &[&str] = &[ + TEMP_STORE_MEMORY_SQL, + BUSY_TIMEOUT_SQL, + WAL_MODE_SQL, + BASELINE_SYNCHRONOUS_SQL, +]; +const MEMORY_SETUP_SQL: &[&str] = &[TEMP_STORE_MEMORY_SQL]; +const ATTR_CACHE_MAX_SIZE: usize = 10000; + +/// Production connection-pool options for local file-backed AgentFS databases. +pub(crate) fn file_backed_connection_pool_options() -> PoolOptions { + PoolOptions { + max_connections: FILE_BACKED_MAX_CONNECTIONS, + ..PoolOptions::default().with_setup_sql(FILE_BACKED_SETUP_SQL.iter().copied()) + } +} + +/// Production connection-pool options for local in-memory AgentFS databases. +pub(crate) fn memory_connection_pool_options() -> PoolOptions { + PoolOptions::single_connection().with_setup_sql(MEMORY_SETUP_SQL.iter().copied()) +} + +async fn checkpoint_wal(conn: &Connection) -> Result<()> { + let _checkpoint_timer = + crate::telemetry::timer(&crate::telemetry::CORE_COUNTERS.wal_checkpoint); + let mut rows = conn.query(WAL_CHECKPOINT_SQL, ()).await?; + while rows.next().await?.is_some() {} + Ok(()) +} + +fn sqlite_sidecar_path(path: &Path, suffix: &str) -> PathBuf { + PathBuf::from(format!("{}{}", path.display(), suffix)) +} + +fn remove_checkpointed_sidecars(path: &Path) -> Result<()> { + let wal = sqlite_sidecar_path(path, "-wal"); + if let Ok(metadata) = std::fs::metadata(&wal) { + if metadata.len() == 0 { + std::fs::remove_file(&wal)?; + } + } + + let shm = sqlite_sidecar_path(path, "-shm"); + if shm.exists() { + std::fs::remove_file(&shm)?; + } + Ok(()) +} + +/// A filesystem backed by SQLite +#[derive(Clone)] +pub struct AgentFS { + pool: ConnectionPool, + db_path: Option>, + chunk_size: usize, + inline_threshold: usize, + /// Cache for directory entry lookups (shared across clones) + dentry_cache: Arc, + /// Cache for negative directory entry lookups (shared across clones) + negative_dentry_cache: Arc, + /// Cache for inode attributes (shared across clones) + attr_cache: Arc, + /// Synchronous pending view, safe to consult while a pooled connection is held. + pending_view: Option, + /// Async drain/enqueue surface. Code holding a pooled connection must not + /// have access to this surface. + write_drain: Option, + /// Concrete batcher retained only for white-box unit tests. + #[cfg(test)] + write_batcher: Option>, + /// Bulk-import transaction sizes observed by white-box tests. + #[cfg(test)] + import_commit_sizes: Arc>>, + /// Tier 4 escape hatch: when false (`AGENTFS_OVERLAY_READS=0`), the SDK + /// behaves like Tier 3 — every pwrite drains, every pread drains, + /// `merge_pending_view` is a no-op. ON by default. + overlay_reads: bool, + /// Typed runtime configuration captured once when the filesystem opens. + core_config: Arc, + /// Open-handle registry, deferred orphan queue, and reap hooks. + lifecycle: Arc, +} + +fn current_timestamp() -> Result<(i64, i64)> { + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + Ok((dur.as_secs() as i64, dur.subsec_nanos() as i64)) +} + +impl AgentFS { + /// Create a new filesystem + pub async fn new(db_path: &str) -> Result { + let db = Builder::new_local(db_path).build().await?; + let pool = if db_path == ":memory:" { + ConnectionPool::with_options(DatabaseType::Local(db), memory_connection_pool_options()) + } else { + ConnectionPool::with_options( + DatabaseType::Local(db), + file_backed_connection_pool_options(), + ) + }; + let db_path = if db_path == ":memory:" { + None + } else { + Some(PathBuf::from(db_path)) + }; + Self::from_pool_with_path_and_config(pool, db_path, CoreConfig::from_env()).await + } + + /// Create a filesystem from a connection pool + pub async fn from_pool(pool: ConnectionPool) -> Result { + Self::from_pool_with_config(pool, CoreConfig::from_env()).await + } + + /// Create a filesystem from a connection pool and explicit core config. + pub async fn from_pool_with_config(pool: ConnectionPool, config: CoreConfig) -> Result { + Self::from_pool_with_path_and_config(pool, None, config).await + } + + pub(crate) async fn from_pool_with_path_and_config( + pool: ConnectionPool, + db_path: Option, + config: CoreConfig, + ) -> Result { + Self::from_pool_with_path_config_and_reap_hooks(pool, db_path, config, Vec::new()).await + } + + pub(crate) async fn from_pool_with_path_config_and_reap_hooks( + pool: ConnectionPool, + db_path: Option, + mut config: CoreConfig, + reap_hooks: Vec>, + ) -> Result { + // finalize() resolves this path for sidecar removal long after the + // caller may have changed the working directory (mount teardown + // chdirs to `/`), so a relative path would silently miss the -wal. + let db_path = db_path.map(std::path::absolute).transpose()?; + let conn = pool.get_connection().await?; + + // Initialize or migrate schema first. The schema module owns DDL and + // stamps SQLite's user-version inside the DDL transaction. + Self::initialize_schema(&conn).await?; + + // Get chunk_size from config (or use default) + let chunk_size = Self::read_chunk_size(&conn).await?; + let inline_threshold = Self::read_inline_threshold(&conn).await?; + config.geometry = Geometry { + chunk_size, + inline_threshold, + }; + let core_config = Arc::new(config); + + let attr_cache = Arc::new(AttrCache::new(ATTR_CACHE_MAX_SIZE)); + // Tier Three Axis D: default the write batcher to ON. CLI callers pass + // the FUSE writeback decision through AgentFSOptions, while SDK callers + // can supply CoreConfig directly. + let (pending_view, write_drain, _write_batcher) = if core_config.batcher.enabled { + let invalidate = { + let attr_cache = Arc::clone(&attr_cache); + Arc::new(move |ino| attr_cache.remove(ino)) as batcher::Invalidate + }; + let batcher = Arc::new(AgentFSWriteBatcher::from_config( + pool.clone(), + chunk_size, + inline_threshold, + invalidate, + &core_config.batcher, + )); + let (pending_view, write_drain) = AgentFSWriteBatcher::split(&batcher); + (Some(pending_view), Some(write_drain), Some(batcher)) + } else { + (None, None, None) + }; + + let lifecycle = Arc::new(Lifecycle::default()); + for hook in reap_hooks { + lifecycle.register_reap_hook(hook); + } + lifecycle.sweep_mount_orphans(&conn).await?; + + let overlay_reads = core_config.overlay_reads; + let fs = Self { + pool, + db_path: db_path.map(Arc::new), + chunk_size, + inline_threshold, + dentry_cache: Arc::new(DentryCache::new(DENTRY_CACHE_MAX_SIZE)), + negative_dentry_cache: Arc::new(NegativeDentryCache::new( + NEGATIVE_DENTRY_CACHE_MAX_SIZE, + )), + attr_cache, + pending_view, + write_drain, + #[cfg(test)] + write_batcher: _write_batcher, + #[cfg(test)] + import_commit_sizes: Arc::new(Mutex::new(Vec::new())), + overlay_reads, + core_config, + lifecycle, + }; + Ok(fs) + } + + /// Get the configured chunk size + pub fn chunk_size(&self) -> usize { + self.chunk_size + } + + /// Get the configured inline threshold. + pub fn inline_threshold(&self) -> usize { + self.inline_threshold + } + + pub fn core_config(&self) -> &CoreConfig { + self.core_config.as_ref() + } + + pub fn partial_origin_policy(&self) -> crate::fs::PartialOriginPolicy { + self.core_config.partial_origin + } + + pub fn register_reap_hook(&self, hook: Arc) -> bool { + self.lifecycle.register_reap_hook(hook) + } + + #[cfg(test)] + pub(crate) fn reap_hook_count(&self) -> usize { + self.lifecycle.reap_hook_count() + } + + /// Get a database connection from the pool + pub async fn get_connection(&self) -> Result { + self.pool.get_connection().await + } + + /// Get the connection pool + pub fn get_pool(&self) -> ConnectionPool { + self.pool.clone() + } + + /// Initialize the database schema + pub async fn initialize_schema(conn: &Connection) -> Result<()> { + schema::require_current(conn).await?; + + // Ensure root directory exists with correct ownership + let mut rows = conn + .query("SELECT ino FROM fs_inode WHERE ino = ?", (ROOT_INO,)) + .await?; + + // SAFETY: getuid/getgid are always safe + #[cfg(unix)] + let (uid, gid) = unsafe { (libc::getuid(), libc::getgid()) }; + #[cfg(not(unix))] + let (uid, gid) = (0u32, 0u32); + + if rows.next().await?.is_none() { + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + let now_secs = dur.as_secs() as i64; + let now_nsec = dur.subsec_nanos() as i64; + conn.execute( + "INSERT INTO fs_inode (ino, mode, nlink, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec) + VALUES (?, ?, 2, ?, ?, 0, ?, ?, ?, ?, ?, ?)", + (ROOT_INO, DEFAULT_DIR_MODE as i64, uid, gid, now_secs, now_secs, now_secs, now_nsec, now_nsec, now_nsec), + ) + .await?; + } else { + // Update existing root inode ownership to current user + conn.execute( + "UPDATE fs_inode SET uid = ?, gid = ? WHERE ino = ?", + (uid, gid, ROOT_INO), + ) + .await?; + } + + Ok(()) + } + + /// Read chunk size from config + async fn read_chunk_size(conn: &Connection) -> Result { + let mut rows = conn + .query("SELECT value FROM fs_config WHERE key = 'chunk_size'", ()) + .await?; + + if let Some(row) = rows.next().await? { + let value = row + .get_value(0) + .ok() + .and_then(|v| match v { + Value::Text(s) => s.parse::().ok(), + Value::Integer(i) => Some(i as usize), + _ => None, + }) + .unwrap_or(DEFAULT_CHUNK_SIZE); + Ok(value) + } else { + Ok(DEFAULT_CHUNK_SIZE) + } + } + + /// Read inline threshold from config + async fn read_inline_threshold(conn: &Connection) -> Result { + let mut rows = conn + .query( + "SELECT value FROM fs_config WHERE key = 'inline_threshold'", + (), + ) + .await?; + + if let Some(row) = rows.next().await? { + let value = row + .get_value(0) + .ok() + .and_then(|v| match v { + Value::Text(s) => s.parse::().ok(), + Value::Integer(i) => Some(i as usize), + _ => None, + }) + .unwrap_or(DEFAULT_INLINE_THRESHOLD); + Ok(value) + } else { + Ok(DEFAULT_INLINE_THRESHOLD) + } + } + + /// Normalize a path + fn normalize_path(&self, path: &str) -> String { + let normalized = path.trim_end_matches('/'); + let normalized = if normalized.is_empty() { + "/" + } else if normalized.starts_with('/') { + normalized + } else { + return format!("/{}", normalized); + }; + + // Handle . and .. components + let components: Vec<&str> = normalized.split('/').filter(|s| !s.is_empty()).collect(); + let mut result = Vec::new(); + + for component in components { + match component { + "." => { + // Current directory - skip it + continue; + } + ".." => { + // Parent directory - only pop if there is a component to pop (don't traverse above root) + if !result.is_empty() { + result.pop(); + } + } + _ => { + result.push(component); + } + } + } + + if result.is_empty() { + "/".to_string() + } else { + format!("/{}", result.join("/")) + } + } + + /// Split path into components + fn split_path(&self, path: &str) -> Vec { + let normalized = self.normalize_path(path); + if normalized == "/" { + return vec![]; + } + normalized + .split('/') + .filter(|p| !p.is_empty()) + .map(|s| s.to_string()) + .collect() + } + + /// Look up a child entry by parent inode and name using a provided connection. + /// + /// This is more efficient than `resolve_path` when you already have the parent inode, + /// as it avoids re-resolving all parent path components. + async fn lookup_child( + &self, + conn: &Connection, + parent_ino: i64, + name: &str, + ) -> Result> { + if let Some(cached_ino) = self.dentry_cache.get(parent_ino, name) { + return Ok(Some(cached_ino)); + } + if self.negative_dentry_cache.contains(parent_ino, name) { + return Ok(None); + } + + let mut stmt = conn + .prepare_cached("SELECT ino FROM fs_dentry WHERE parent_ino = ? AND name = ?") + .await?; + let mut rows = stmt.query((parent_ino, name)).await?; + + let mut found_ino = None; + let mut row_count = 0; + + while let Some(row) = rows.next().await? { + found_ino = Some( + row.get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .ok_or_else(|| { + FsError::Corrupt(format!( + "invalid ino for dentry {parent_ino}/{name}: expected integer" + )) + })?, + ); + row_count += 1; + } + + if row_count > 1 { + return Err(FsError::InvalidPath.into()); + } + + if let Some(ino) = found_ino { + self.cache_dentry(parent_ino, name, ino); + } else { + self.cache_negative_dentry(parent_ino, name); + } + + Ok(found_ino) + } + + fn cache_attr(&self, stats: Stats) { + self.attr_cache.insert(stats); + } + + fn pending_generation(&self, ino: i64) -> Option { + self.pending_view + .as_ref() + .map(|view| view.pending_generation(ino)) + } + + fn cache_attr_if_pending_generation( + &self, + stats: Stats, + generation: Option, + ) { + if let (Some(view), Some(generation)) = (&self.pending_view, generation) { + if view.pending_generation(stats.ino) != generation { + return; + } + } + self.cache_attr(stats); + } + + pub(crate) fn invalidate_attr(&self, ino: i64) { + self.attr_cache.remove(ino); + } + + /// Drain pending batched writes for one inode. + pub async fn drain_inode_writes(&self, ino: i64) -> Result<()> { + if let Some(drain) = &self.write_drain { + drain.drain_inode(ino).await?; + } + Ok(()) + } + + /// Prelude shared by chmod / chown / utimens. + /// + /// Legacy drain-on-setattr behaviour synchronously commits the inode's + /// pending batched writes so the deferred data commit can never re-stamp + /// mtime/ctime after the explicit attribute change. With FUSE writeback + /// caching the kernel issues one SETATTR per written file, so that drain + /// serialised a SQLite commit per file on the clone path. + /// + /// Default: skip the drain and instead mark the pending entry so the + /// eventual batched commit preserves mtime/ctime (`mark_times_explicit` / + /// `preserve_times`). The mark happens BEFORE the caller's fs_inode + /// UPDATE; combined with the commit path re-reading the flag after it + /// holds the SQLite write lock, the explicitly-set attributes win in every + /// interleaving. + /// + /// The deferral requires Tier-4 overlay reads: with + /// overlay reads disabled, getattr/size are served straight from + /// SQLite with no pending-size merge, so the legacy drain is kept to make + /// the just-written size visible at close time (git reads files by + /// `st_size`). + async fn prepare_attr_change(&self, ino: i64) -> Result<()> { + if self.core_config.drain_on_setattr || !self.overlay_reads { + return self.drain_inode_writes(ino).await; + } + if let Some(drain) = &self.write_drain { + drain.mark_times_explicit(ino); + } + Ok(()) + } + + /// Tier Four helper: merge the batcher's pending state into a `Stats` row + /// read from SQLite, so callers that hold a pool connection don't need to + /// drain (which would deadlock on single-conn pools): + /// - `size` is OR-ed with the pending max write end (mirrors the logic in + /// `AgentFS::getattr` and `AgentFSFile::pread`); + /// - explicitly-set times stashed by `utimens` (`PendingTimeChange`) are + /// overlaid so a deferred SETATTR is visible before its drain commits. + /// + /// Fast-paths when the batcher has nothing pending for this inode (Tier 4 + /// read hot path: most reads pay zero cost beyond a read-lock HashMap hit). + fn merge_pending_view(&self, ino: i64, stats: Option<&mut Stats>) { + let Some(stats) = stats else { + return; + }; + // Escape hatch: when overlay reads are disabled, callers' SQLite + // size view is already authoritative because pwrites went straight + // to SQLite (see AgentFSFile::pwrite) and utimens never stashes. + // No merge needed. + if !self.overlay_reads { + return; + } + let Some(view) = &self.pending_view else { + return; + }; + view.merge_into_stats(ino, stats); + } + + /// Drain all pending batched writes for this AgentFS instance. + pub async fn drain_all(&self) -> Result<()> { + if let Some(drain) = &self.write_drain { + drain.drain_all().await?; + } + let conn = self.pool.get_connection().await?; + checkpoint_wal(&conn).await?; + Ok(()) + } + + /// Drain all writes and leave the database in single-file journal mode for clean shutdown. + pub async fn finalize(&self) -> Result<()> { + self.process_deferred_reaps().await?; + self.drain_all().await?; + if let Some(path) = &self.db_path { + remove_checkpointed_sidecars(path.as_ref())?; + } + Ok(()) + } + + /// Reap inodes whose deletion unlink/rename deferred because open + /// handles existed (POSIX unlink-while-open). Runs opportunistically at + /// namespace mutations and at finalize; a crash is covered by the + /// nlink=0 sweep at mount. + pub async fn process_deferred_reaps(&self) -> Result<()> { + let reaped = self + .lifecycle + .process_deferred_reaps(&self.pool, |ino| { + self.discard_pending_for_reaped_inode(ino); + }) + .await?; + for ino in reaped { + self.invalidate_attr(ino); + } + Ok(()) + } + + async fn reap_inode_with_conn(&self, conn: &Connection, ino: i64) -> Result { + self.lifecycle.reap_inode_with_conn(conn, ino).await + } + + /// Drop batcher state for an inode that is being reaped. Inline unlink / + /// rename-replace callers invoke this only after their metadata + /// transaction commits, so a reap-hook rollback leaves pending writes + /// intact with the still-live inode. Deferred reaps call it before the + /// transaction opens because the inode is already nlink=0 and invisible. + fn discard_pending_for_reaped_inode(&self, ino: i64) { + if let Some(drain) = &self.write_drain { + drain.discard_pending(ino); + } + } + + fn invalidate_parent_attr(&self, parent_ino: i64) { + self.invalidate_attr(parent_ino); + } + + fn invalidate_dentry(&self, parent_ino: i64, name: &str) { + self.dentry_cache.remove(parent_ino, name); + self.negative_dentry_cache.remove(parent_ino, name); + } + + fn cache_dentry(&self, parent_ino: i64, name: &str, child_ino: i64) { + self.negative_dentry_cache.remove(parent_ino, name); + self.dentry_cache.insert(parent_ino, name, child_ino); + } + + fn cache_negative_dentry(&self, parent_ino: i64, name: &str) { + self.dentry_cache.remove(parent_ino, name); + self.negative_dentry_cache.insert(parent_ino, name); + } + + /// Get link count for an inode + async fn get_link_count(&self, conn: &Connection, ino: i64) -> Result { + store::link_count(conn, ino).await + } + + /// Get file attributes by inode using an existing connection + async fn getattr_with_conn(&self, conn: &Connection, ino: i64) -> Result> { + if let Some(stats) = self.attr_cache.get(ino) { + return Ok(Some(stats)); + } + + let generation = self.pending_generation(ino); + if let Some(mut stats) = store::getattr(conn, ino).await? { + self.merge_pending_view(ino, Some(&mut stats)); + self.cache_attr_if_pending_generation(stats.clone(), generation); + Ok(Some(stats)) + } else { + Ok(None) + } + } + + /// Resolve a path to an inode number + async fn resolve_path(&self, path: &str) -> Result> { + let conn = self.pool.get_connection().await?; + self.resolve_path_with_conn(&conn, path).await + } + + /// Resolve a path to an inode number using a provided connection + async fn resolve_path_with_conn(&self, conn: &Connection, path: &str) -> Result> { + let components = self.split_path(path); + crate::telemetry::record_path_resolution(components.len() as u64); + if components.is_empty() { + return Ok(Some(ROOT_INO)); + } + + let mut statement: Option = None; + let mut current_ino = ROOT_INO; + for component in components { + // Check cache first + if let Some(cached_ino) = self.dentry_cache.get(current_ino, &component) { + current_ino = cached_ino; + continue; + } + if self.negative_dentry_cache.contains(current_ino, &component) { + crate::telemetry::record_negative_lookup(); + return Ok(None); + } + + // Cache miss - query database + if let Some(statement) = &mut statement { + statement.reset()?; + } else { + statement = Some( + conn.prepare_cached( + "SELECT ino FROM fs_dentry WHERE parent_ino = ? AND name = ?", + ) + .await?, + ); + } + let statement = statement.as_mut().expect("statement was set above"); + let mut rows = statement.query((current_ino, component.as_str())).await?; + + let mut found_row = None; + let mut row_count = 0; + + while let Some(row) = rows.next().await? { + found_row = Some(row); + row_count += 1; + } + + if row_count > 1 { + return Err(FsError::InvalidPath.into()); + } + + if let Some(row) = found_row { + let child_ino = row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .ok_or_else(|| FsError::Corrupt("invalid ino: expected integer".to_string()))?; + + // Populate cache + self.cache_dentry(current_ino, &component, child_ino); + current_ino = child_ino; + } else { + crate::telemetry::record_negative_lookup(); + self.cache_negative_dentry(current_ino, &component); + return Ok(None); + } + } + + Ok(Some(current_ino)) + } + + /// Resolve a path to its parent directory inode and final component name. + /// + /// This is the canonical parent/name resolver backing every path-based + /// mutation helper; external path consumers (e.g. the CLI MCP server) + /// must use it rather than re-deriving parent inodes. + pub async fn resolve_parent_and_name(&self, path: &str) -> Result<(i64, String)> { + let path = self.normalize_path(path); + let components = self.split_path(&path); + if components.is_empty() { + return Err(FsError::RootOperation.into()); + } + + let parent_path = match components.len() { + 1 => "/".to_string(), + _ => format!("/{}", components[..components.len() - 1].join("/")), + }; + let parent_ino = self + .resolve_path(&parent_path) + .await? + .ok_or(FsError::NotFound)?; + let name = components.last().cloned().ok_or(FsError::InvalidPath)?; + Ok((parent_ino, name)) + } + + /// List directory contents + pub async fn readdir(&self, ino: i64) -> Result>> { + let conn = self.pool.get_connection().await?; + let mut rows = conn + .query( + "SELECT name FROM fs_dentry WHERE parent_ino = ? ORDER BY name", + (ino,), + ) + .await?; + + let mut entries = Vec::new(); + while let Some(row) = rows.next().await? { + let name = row + .get_value(0) + .ok() + .and_then(|v| { + if let Value::Text(s) = v { + Some(s.clone()) + } else { + None + } + }) + .unwrap_or_default(); + if !name.is_empty() { + entries.push(name); + } + } + + Ok(Some(entries)) + } + + /// Read the target of a symbolic link + pub async fn readlink(&self, path: &str) -> Result> { + let conn = self.pool.get_connection().await?; + self.readlink_with_conn(&conn, path).await + } + + /// Read the target of a symbolic link using a provided connection + async fn readlink_with_conn(&self, conn: &Connection, path: &str) -> Result> { + let path = self.normalize_path(path); + + let ino = match self.resolve_path_with_conn(conn, &path).await? { + Some(ino) => ino, + None => return Ok(None), + }; + + // Check if it's a symlink by querying the inode + if let Some(mode) = store::mode(conn, ino).await? { + if (mode & S_IFMT) != S_IFLNK { + return Err(FsError::NotASymlink.into()); + } + } else { + return Ok(None); + } + + // Read target from fs_symlink table + let mut rows = conn + .query("SELECT target FROM fs_symlink WHERE ino = ?", (ino,)) + .await?; + + if let Some(row) = rows.next().await? { + let target = row + .get_value(0) + .ok() + .and_then(|v| match v { + Value::Text(s) => Some(s.to_string()), + _ => None, + }) + .ok_or(FsError::InvalidPath)?; + Ok(Some(target)) + } else { + Ok(None) + } + } + + /// Get filesystem statistics + /// + /// Returns the total number of inodes and bytes used by file contents. + pub async fn statfs(&self) -> Result { + self.drain_all().await?; + let conn = self.pool.get_connection().await?; + // Count total inodes + let mut stmt = conn.prepare_cached("SELECT COUNT(*) FROM fs_inode").await?; + let mut rows = stmt.query(()).await?; + + let inodes = if let Some(row) = rows.next().await? { + row.get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(0) as u64 + } else { + 0 + }; + + // Sum total bytes used (from file sizes in inodes) + let mut stmt = conn + .prepare_cached("SELECT COALESCE(SUM(size), 0) FROM fs_inode") + .await?; + let mut rows = stmt.query(()).await?; + + let bytes_used = if let Some(row) = rows.next().await? { + row.get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(0) as u64 + } else { + 0 + }; + + Ok(FilesystemStats { inodes, bytes_used }) + } + + /// Open a file and return a file handle. + /// + /// The returned handle can be used for efficient read/write/fsync operations + /// without requiring path lookups on each operation. + pub async fn open(&self, path: &str) -> Result { + let path = self.normalize_path(path); + let ino = self.resolve_path(&path).await?.ok_or(FsError::NotFound)?; + + Ok(Arc::new(AgentFSFile { + pool: self.pool.clone(), + ino, + chunk_size: self.chunk_size, + inline_threshold: self.inline_threshold, + attr_cache: self.attr_cache.clone(), + pending_view: self.pending_view.clone(), + write_drain: self.write_drain.clone(), + overlay_reads: self.overlay_reads, + _open_guard: Some(self.lifecycle.guard(ino)), + })) + } + + /// Get the number of chunks for a given inode (for testing). + /// Drains any pending batched writes first so the returned count reflects + /// the full committed state — Tier 4 deferred SQLite commits until fsync + /// or timer, so tests that inspect `fs_data` directly need a sync point. + #[cfg(test)] + async fn get_chunk_count(&self, ino: i64) -> Result { + self.drain_inode_writes(ino).await?; + let conn = self.pool.get_connection().await?; + let mut rows = conn + .query("SELECT COUNT(*) FROM fs_data WHERE ino = ?", (ino,)) + .await?; + + if let Some(row) = rows.next().await? { + Ok(row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(0)) + } else { + Ok(0) + } + } + + #[cfg(test)] + async fn get_storage_state(&self, ino: i64) -> Result<(i64, Option>)> { + self.drain_inode_writes(ino).await?; + let conn = self.pool.get_connection().await?; + let mut rows = conn + .query( + "SELECT storage_kind, data_inline FROM fs_inode WHERE ino = ?", + (ino,), + ) + .await?; + + if let Some(row) = rows.next().await? { + let storage_kind = row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(STORAGE_CHUNKED); + let data_inline = match row.get_value(1) { + Ok(Value::Blob(data)) => Some(data), + _ => None, + }; + Ok((storage_kind, data_inline)) + } else { + Err(FsError::NotFound.into()) + } + } +} + +#[cfg(test)] +// Keep the extracted test body byte-for-byte; this feature is a pure move. +#[rustfmt::skip] +#[path = "tests.rs"] +mod agentfs_tests; diff --git a/crates/agentfs-core/src/fs/agentfs/path_api.rs b/crates/agentfs-core/src/fs/agentfs/path_api.rs new file mode 100644 index 00000000..cd794d0b --- /dev/null +++ b/crates/agentfs-core/src/fs/agentfs/path_api.rs @@ -0,0 +1,113 @@ +//! Path-based AgentFS convenience delegates. +//! +//! These are the six live path helpers retained after M2. Each resolves a +//! path to the canonical inode-oriented `FileSystem` trait operation so +//! metadata, batching, and mutation semantics have one implementation. + +use std::path::Path; + +use crate::fs::FileSystem; + +use super::*; + +impl AgentFS { + /// Get file statistics, following symlinks. + pub async fn stat(&self, path: &str) -> Result> { + let path = self.normalize_path(path); + let mut current_path = path; + for _ in 0..40 { + let ino = match self.resolve_path(¤t_path).await? { + Some(ino) => ino, + None => return Ok(None), + }; + let Some(stats) = FileSystem::getattr(self, ino).await? else { + return Ok(None); + }; + if !stats.is_symlink() { + return Ok(Some(stats)); + } + + let target = FileSystem::readlink(self, ino) + .await? + .ok_or(FsError::NotFound)?; + current_path = if target.starts_with('/') { + target + } else { + let base_path = Path::new(¤t_path); + let parent = base_path.parent().unwrap_or(Path::new("/")); + parent.join(&target).to_string_lossy().into_owned() + }; + current_path = self.normalize_path(¤t_path); + } + + Err(FsError::SymlinkLoop.into()) + } + + /// Create a directory + pub async fn mkdir(&self, path: &str, uid: u32, gid: u32) -> Result<()> { + let (parent_ino, name) = self.resolve_parent_and_name(path).await?; + FileSystem::mkdir(self, parent_ino, &name, DEFAULT_DIR_MODE, uid, gid).await?; + Ok(()) + } + + /// Create a new empty file with the specified mode and ownership. + /// + /// This is an optimized path for FUSE create() that combines inode creation, + /// dentry creation, and file handle opening in a single operation. + /// Returns both Stats and an open file handle. + pub async fn create_file( + &self, + path: &str, + mode: u32, + uid: u32, + gid: u32, + ) -> Result<(Stats, BoxedFile)> { + let (parent_ino, name) = self.resolve_parent_and_name(path).await?; + FileSystem::create_file(self, parent_ino, &name, mode, uid, gid).await + } + + /// Read data from a file + pub async fn read_file(&self, path: &str) -> Result>> { + let stats = match self.stat(path).await? { + Some(stats) => stats, + None => return Ok(None), + }; + let file = FileSystem::open(self, stats.ino, libc::O_RDONLY).await?; + let size = u64::try_from(stats.size).unwrap_or(0); + Ok(Some(file.pread(0, size).await?)) + } + + /// Remove a file or empty directory + pub async fn remove(&self, path: &str) -> Result<()> { + let (parent_ino, name) = self.resolve_parent_and_name(path).await?; + let stats = FileSystem::lookup(self, parent_ino, &name) + .await? + .ok_or(FsError::NotFound)?; + if stats.is_directory() { + FileSystem::rmdir(self, parent_ino, &name).await + } else { + FileSystem::unlink(self, parent_ino, &name).await + } + } + + /// Synchronize file data to persistent storage + /// + /// Temporarily enables FULL synchronous mode, runs a transaction to force + /// a checkpoint, then restores NORMAL mode. This ensures durability while + /// maintaining high performance for normal operations. + /// + pub async fn fsync(&self) -> Result<()> { + FileSystem::drain_all(self).await?; + let conn = self.pool.get_connection().await?; + conn.prepare_cached(DURABLE_SYNCHRONOUS_SQL) + .await? + .execute(()) + .await?; + checkpoint_wal(&conn).await?; + conn.prepare_cached(BASELINE_SYNCHRONOUS_SQL) + .await? + .execute(()) + .await?; + Ok(()) + } +} diff --git a/crates/agentfs-core/src/fs/agentfs/store.rs b/crates/agentfs-core/src/fs/agentfs/store.rs new file mode 100644 index 00000000..74788e81 --- /dev/null +++ b/crates/agentfs-core/src/fs/agentfs/store.rs @@ -0,0 +1,938 @@ +use std::collections::BTreeMap; + +use async_trait::async_trait; +use turso::{Connection, Value}; + +use crate::config::Geometry; +use crate::error::{Error, Result}; +use crate::fs::{FsError, Stats}; + +use super::batcher::{write_commit_time_sets, PendingTimeChange}; +use super::{current_timestamp, STORAGE_CHUNKED, STORAGE_INLINE}; + +pub(super) struct FileStorage { + pub(super) size: u64, + storage_kind: i64, + inline_data: Option>, +} + +pub(in crate::fs) struct WriteRangeRef<'a> { + pub(in crate::fs) offset: u64, + pub(in crate::fs) data: &'a [u8], +} + +#[async_trait] +pub(in crate::fs) trait ChunkWriteHooks: Send + Sync { + async fn seed_missing_chunk( + &self, + conn: &Connection, + ino: i64, + geometry: Geometry, + chunk_index: i64, + ) -> Result>>; + + async fn chunk_written(&self, conn: &Connection, ino: i64, chunk_index: i64) -> Result<()>; +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(super) struct NormalizedWriteRange { + pub(super) offset: u64, + pub(super) data: Vec, +} + +impl NormalizedWriteRange { + pub(super) fn end(&self) -> u64 { + self.offset + self.data.len() as u64 + } +} + +pub(super) fn normalize_write_ranges( + ranges: &[WriteRangeRef<'_>], +) -> Result> { + let mut merged_ranges: BTreeMap> = BTreeMap::new(); + + for range in ranges { + if range.data.is_empty() { + continue; + } + + let data_len = u64::try_from(range.data.len()) + .map_err(|_| Error::Internal("file write length overflow".to_string()))?; + let write_start = range.offset; + let write_end = write_start + .checked_add(data_len) + .ok_or_else(|| Error::Internal("file write offset overflow".to_string()))?; + let mut start = write_start; + let mut end = write_end; + let mut existing_ranges = Vec::new(); + + if let Some((&prev_start, prev_data)) = merged_ranges.range(..=write_start).next_back() { + let prev_end = prev_start + .checked_add(prev_data.len() as u64) + .ok_or_else(|| Error::Internal("file write offset overflow".to_string()))?; + + if prev_end >= write_start { + let prev_data = prev_data.clone(); + merged_ranges.remove(&prev_start); + + start = prev_start; + end = end.max(prev_end); + existing_ranges.push((prev_start, prev_data)); + } + } + + loop { + let next = merged_ranges + .range(start..) + .next() + .map(|(&next_start, next_data)| (next_start, next_data.clone())); + + let Some((next_start, next_data)) = next else { + break; + }; + + if next_start > end { + break; + } + + let next_end = next_start + .checked_add(next_data.len() as u64) + .ok_or_else(|| Error::Internal("file write offset overflow".to_string()))?; + merged_ranges.remove(&next_start); + + end = end.max(next_end); + existing_ranges.push((next_start, next_data)); + } + + let merged_len = usize::try_from(end - start) + .map_err(|_| Error::Internal("file write range too large".to_string()))?; + let mut merged = vec![0; merged_len]; + for (range_start, range_data) in existing_ranges { + let range_offset = usize::try_from(range_start - start) + .map_err(|_| Error::Internal("file write range too large".to_string()))?; + merged[range_offset..range_offset + range_data.len()].copy_from_slice(&range_data); + } + + let write_offset = usize::try_from(write_start - start) + .map_err(|_| Error::Internal("file write range too large".to_string()))?; + merged[write_offset..write_offset + range.data.len()].copy_from_slice(range.data); + + merged_ranges.insert(start, merged); + } + + Ok(merged_ranges + .into_iter() + .map(|(offset, data)| NormalizedWriteRange { offset, data }) + .collect()) +} + +pub(super) fn dense_after_inline_write_batch( + current_size: u64, + new_size: u64, + ranges: &[NormalizedWriteRange], +) -> bool { + let mut covered_end = current_size; + + for range in ranges { + let range_end = range.end(); + if range_end <= covered_end { + continue; + } + if range.offset > covered_end { + return false; + } + covered_end = range_end; + if covered_end >= new_size { + return true; + } + } + + covered_end >= new_size +} + +pub(super) async fn file_storage(conn: &Connection, ino: i64) -> Result { + let mut stmt = conn + .prepare_cached("SELECT size, storage_kind, data_inline FROM fs_inode WHERE ino = ?") + .await?; + let mut rows = stmt.query((ino,)).await?; + + if let Some(row) = rows.next().await? { + let size = required_u64(&row, 0, "size")?; + let storage_kind = required_i64(&row, 1, "storage_kind")?; + if storage_kind != STORAGE_CHUNKED && storage_kind != STORAGE_INLINE { + return Err(corrupt_column("storage_kind", "unknown storage kind")); + } + let inline_data = match row.get_value(2) { + Ok(Value::Blob(data)) => Some(data), + Ok(Value::Null) => None, + Ok(_) | Err(_) => return Err(corrupt_column("data_inline", "expected blob or null")), + }; + if storage_kind == STORAGE_INLINE && inline_data.is_none() { + return Err(corrupt_column( + "data_inline", + "inline file missing inline data", + )); + } + Ok(FileStorage { + size, + storage_kind, + inline_data, + }) + } else { + Err(FsError::NotFound.into()) + } +} + +// Architecture §2.1 exposes `read` as the free-function store seam. The +// AgentFSFile hot path uses `read_from_storage` after it has already fetched +// metadata, avoiding a duplicate metadata query. +#[allow(dead_code)] +pub(in crate::fs) async fn read( + conn: &Connection, + ino: i64, + geometry: Geometry, + offset: u64, + size: u64, +) -> Result> { + let metadata = file_storage(conn, ino).await?; + read_from_storage(conn, ino, geometry, &metadata, offset, size).await +} + +pub(super) async fn read_from_storage( + conn: &Connection, + ino: i64, + geometry: Geometry, + metadata: &FileStorage, + offset: u64, + size: u64, +) -> Result> { + if offset >= metadata.size || size == 0 { + return Ok(Vec::new()); + } + + let size = std::cmp::min(size, metadata.size - offset); + if metadata.storage_kind == STORAGE_INLINE { + let mut result = Vec::with_capacity(size as usize); + let inline_data = metadata.inline_data.clone().unwrap_or_default(); + let start = offset as usize; + let requested = size as usize; + + if start < inline_data.len() { + let available = std::cmp::min(inline_data.len() - start, requested); + result.extend_from_slice(&inline_data[start..start + available]); + } + + if result.len() < requested { + result.resize(requested, 0); + } + + return Ok(result); + } + + read_chunked(conn, ino, geometry, offset, size).await +} + +async fn read_chunked( + conn: &Connection, + ino: i64, + geometry: Geometry, + offset: u64, + size: u64, +) -> Result> { + let chunk_size = geometry.chunk_size as u64; + let start_chunk = offset / chunk_size; + let end_chunk = (offset + size).saturating_sub(1) / chunk_size; + + let mut stmt = conn + .prepare_cached("SELECT chunk_index, data FROM fs_data WHERE ino = ? AND chunk_index >= ? AND chunk_index <= ? ORDER BY chunk_index") + .await?; + crate::telemetry::record_chunk_read_query(); + let mut rows = stmt + .query((ino, start_chunk as i64, end_chunk as i64)) + .await?; + + let mut result = Vec::with_capacity(size as usize); + let start_offset_in_chunk = (offset % chunk_size) as usize; + let mut next_expected_chunk = start_chunk; + let mut chunks_read = 0u64; + + while let Some(row) = rows.next().await? { + chunks_read += 1; + let chunk_index = required_u64(&row, 0, "fs_data.chunk_index")?; + + while next_expected_chunk < chunk_index && result.len() < size as usize { + let skip = if next_expected_chunk == start_chunk { + start_offset_in_chunk + } else { + 0 + }; + let zeros_needed = + std::cmp::min(chunk_size as usize - skip, size as usize - result.len()); + result.extend(std::iter::repeat_n(0u8, zeros_needed)); + next_expected_chunk += 1; + } + + let chunk_data = match row.get_value(1) { + Ok(Value::Blob(data)) => data, + Ok(_) | Err(_) => return Err(corrupt_column("fs_data.data", "expected blob")), + }; + let skip = if chunk_index == start_chunk { + start_offset_in_chunk + } else { + 0 + }; + if skip >= chunk_data.len() { + let zeros_needed = + std::cmp::min(chunk_size as usize - skip, size as usize - result.len()); + result.extend(std::iter::repeat_n(0u8, zeros_needed)); + } else { + let remaining = size as usize - result.len(); + let take = std::cmp::min(chunk_data.len() - skip, remaining); + result.extend_from_slice(&chunk_data[skip..skip + take]); + + let chunk_end = skip + take; + if chunk_end < chunk_size as usize && result.len() < size as usize { + let zeros_needed = std::cmp::min( + chunk_size as usize - chunk_end, + size as usize - result.len(), + ); + result.extend(std::iter::repeat_n(0u8, zeros_needed)); + } + } + next_expected_chunk = chunk_index + 1; + } + + if result.len() < size as usize { + result.resize(size as usize, 0); + } + + crate::telemetry::record_chunk_read_chunks(chunks_read); + Ok(result) +} + +/// `preserve_times`: when true (deferred batcher commits racing an explicit +/// chmod/chown/utimens), leave mtime/ctime untouched instead of stamping +/// the commit time. `explicit_times`: stashed setattr values folded into the +/// inode UPDATE itself (see `write_commit_time_sets`). +#[derive(Default)] +struct WriteOptions<'a> { + preserve_times: bool, + explicit_times: Option<&'a PendingTimeChange>, + hooks: Option<&'a dyn ChunkWriteHooks>, + force_chunked: bool, +} + +pub(super) async fn write_ranges( + conn: &Connection, + ino: i64, + geometry: Geometry, + ranges: &[WriteRangeRef<'_>], + preserve_times: bool, + explicit_times: Option<&PendingTimeChange>, +) -> Result<()> { + write_ranges_inner( + conn, + ino, + geometry, + ranges, + WriteOptions { + preserve_times, + explicit_times, + ..WriteOptions::default() + }, + ) + .await +} + +pub(in crate::fs) async fn write_ranges_with_chunk_hooks( + conn: &Connection, + ino: i64, + geometry: Geometry, + ranges: &[WriteRangeRef<'_>], + hooks: &dyn ChunkWriteHooks, +) -> Result<()> { + write_ranges_inner( + conn, + ino, + geometry, + ranges, + WriteOptions { + hooks: Some(hooks), + force_chunked: true, + ..WriteOptions::default() + }, + ) + .await +} + +async fn write_ranges_inner( + conn: &Connection, + ino: i64, + geometry: Geometry, + ranges: &[WriteRangeRef<'_>], + options: WriteOptions<'_>, +) -> Result<()> { + let ranges = normalize_write_ranges(ranges)?; + if ranges.is_empty() { + return Ok(()); + } + + let metadata = file_storage(conn, ino).await?; + let write_end = ranges + .iter() + .map(NormalizedWriteRange::end) + .max() + .unwrap_or(metadata.size); + let new_size = std::cmp::max(metadata.size, write_end); + + if !options.force_chunked + && metadata.storage_kind == STORAGE_INLINE + && new_size <= geometry.inline_threshold as u64 + && dense_after_inline_write_batch(metadata.size, new_size, &ranges) + { + let mut inline_data = metadata.inline_data.unwrap_or_default(); + inline_data.resize(metadata.size as usize, 0); + inline_data.resize(new_size as usize, 0); + for range in &ranges { + let start = range.offset as usize; + inline_data[start..start + range.data.len()].copy_from_slice(&range.data); + } + + conn.execute("DELETE FROM fs_data WHERE ino = ?", (ino,)) + .await?; + let mut sets = vec!["size = ?", "data_inline = ?", "storage_kind = ?"]; + let mut values: Vec = vec![ + Value::Integer(new_size as i64), + Value::Blob(inline_data), + Value::Integer(STORAGE_INLINE), + ]; + let (time_sets, time_values) = + write_commit_time_sets(options.preserve_times, options.explicit_times)?; + sets.extend(time_sets); + values.extend(time_values); + values.push(Value::Integer(ino)); + let sql = format!("UPDATE fs_inode SET {} WHERE ino = ?", sets.join(", ")); + conn.execute(&sql, values).await?; + return Ok(()); + } + + let mut chunked_ranges = Vec::new(); + if metadata.storage_kind == STORAGE_INLINE { + let mut inline_data = metadata.inline_data.unwrap_or_default(); + inline_data.resize(metadata.size as usize, 0); + conn.execute("DELETE FROM fs_data WHERE ino = ?", (ino,)) + .await?; + if !inline_data.is_empty() { + chunked_ranges.push(NormalizedWriteRange { + offset: 0, + data: inline_data, + }); + } + } else { + conn.execute( + "UPDATE fs_inode SET data_inline = NULL, storage_kind = ? WHERE ino = ?", + (STORAGE_CHUNKED, ino), + ) + .await?; + } + + chunked_ranges.extend(ranges); + write_ranges_chunked(conn, ino, geometry, &chunked_ranges, options.hooks).await?; + + let mut sets = vec!["size = ?", "data_inline = NULL", "storage_kind = ?"]; + let mut values: Vec = vec![ + Value::Integer(new_size as i64), + Value::Integer(STORAGE_CHUNKED), + ]; + let (time_sets, time_values) = + write_commit_time_sets(options.preserve_times, options.explicit_times)?; + sets.extend(time_sets); + values.extend(time_values); + values.push(Value::Integer(ino)); + let sql = format!("UPDATE fs_inode SET {} WHERE ino = ?", sets.join(", ")); + conn.execute(&sql, values).await?; + + Ok(()) +} + +pub(super) async fn truncate( + conn: &Connection, + ino: i64, + geometry: Geometry, + new_size: u64, +) -> Result<()> { + truncate_inner(conn, ino, geometry, new_size, false, None).await +} + +pub(in crate::fs) async fn truncate_with_chunk_hooks( + conn: &Connection, + ino: i64, + geometry: Geometry, + new_size: u64, + hooks: &dyn ChunkWriteHooks, +) -> Result<()> { + truncate_inner(conn, ino, geometry, new_size, true, Some(hooks)).await +} + +async fn truncate_inner( + conn: &Connection, + ino: i64, + geometry: Geometry, + new_size: u64, + force_chunked: bool, + hooks: Option<&dyn ChunkWriteHooks>, +) -> Result<()> { + let metadata = file_storage(conn, ino).await?; + + if metadata.storage_kind == STORAGE_INLINE { + if !force_chunked && new_size <= geometry.inline_threshold as u64 { + let mut inline_data = metadata.inline_data.unwrap_or_default(); + inline_data.resize(metadata.size as usize, 0); + inline_data.resize(new_size as usize, 0); + conn.execute("DELETE FROM fs_data WHERE ino = ?", (ino,)) + .await?; + let (now_secs, now_nsec) = current_timestamp()?; + conn.execute( + "UPDATE fs_inode SET size = ?, data_inline = ?, storage_kind = ?, mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?", + ( + new_size as i64, + Value::Blob(inline_data), + STORAGE_INLINE, + now_secs, + now_secs, + now_nsec, + now_nsec, + ino, + ), + ) + .await?; + return Ok(()); + } + + let mut inline_data = metadata.inline_data.unwrap_or_default(); + inline_data.resize(metadata.size as usize, 0); + transition_inline_to_chunked(conn, ino, geometry, &inline_data, hooks).await?; + truncate_chunked_data(conn, ino, geometry, metadata.size, new_size, hooks).await?; + update_chunked_truncate_metadata(conn, ino, new_size).await?; + return Ok(()); + } + + if !force_chunked && new_size <= geometry.inline_threshold as u64 { + if let Some(inline_data) = + read_dense_prefix_for_inline(conn, ino, geometry, new_size).await? + { + conn.execute("DELETE FROM fs_data WHERE ino = ?", (ino,)) + .await?; + let (now_secs, now_nsec) = current_timestamp()?; + conn.execute( + "UPDATE fs_inode SET size = ?, data_inline = ?, storage_kind = ?, mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?", + ( + new_size as i64, + Value::Blob(inline_data), + STORAGE_INLINE, + now_secs, + now_secs, + now_nsec, + now_nsec, + ino, + ), + ) + .await?; + return Ok(()); + } + } + + truncate_chunked_data(conn, ino, geometry, metadata.size, new_size, hooks).await?; + update_chunked_truncate_metadata(conn, ino, new_size).await?; + Ok(()) +} + +async fn transition_inline_to_chunked( + conn: &Connection, + ino: i64, + geometry: Geometry, + inline_data: &[u8], + hooks: Option<&dyn ChunkWriteHooks>, +) -> Result<()> { + conn.execute("DELETE FROM fs_data WHERE ino = ?", (ino,)) + .await?; + + if !inline_data.is_empty() { + write_data_at_offset(conn, ino, geometry, 0, inline_data, hooks).await?; + } + + conn.execute( + "UPDATE fs_inode SET data_inline = NULL, storage_kind = ? WHERE ino = ?", + (STORAGE_CHUNKED, ino), + ) + .await?; + + Ok(()) +} + +async fn read_dense_prefix_for_inline( + conn: &Connection, + ino: i64, + geometry: Geometry, + new_size: u64, +) -> Result>> { + if new_size == 0 { + return Ok(Some(Vec::new())); + } + + let chunk_size = geometry.chunk_size as u64; + let last_chunk = (new_size - 1) / chunk_size; + let mut inline_data = Vec::with_capacity(new_size as usize); + + let mut stmt = conn + .prepare_cached("SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?") + .await?; + for chunk_idx in 0..=last_chunk { + stmt.reset()?; + let mut rows = stmt.query((ino, chunk_idx as i64)).await?; + let Some(row) = rows.next().await? else { + return Ok(None); + }; + let chunk_data = match row.get_value(0) { + Ok(Value::Blob(data)) => data, + _ => return Ok(None), + }; + let remaining = new_size as usize - inline_data.len(); + let needed = std::cmp::min(geometry.chunk_size, remaining); + if chunk_data.len() < needed { + return Ok(None); + } + inline_data.extend_from_slice(&chunk_data[..needed]); + } + + Ok(Some(inline_data)) +} + +async fn truncate_chunked_data( + conn: &Connection, + ino: i64, + geometry: Geometry, + current_size: u64, + new_size: u64, + hooks: Option<&dyn ChunkWriteHooks>, +) -> Result<()> { + let chunk_size = geometry.chunk_size as u64; + + if new_size == 0 { + conn.execute("DELETE FROM fs_data WHERE ino = ?", (ino,)) + .await?; + } else if new_size < current_size { + let last_chunk_idx = (new_size - 1) / chunk_size; + + conn.execute( + "DELETE FROM fs_data WHERE ino = ? AND chunk_index > ?", + (ino, last_chunk_idx as i64), + ) + .await?; + + let end_in_last_chunk = ((new_size - 1) % chunk_size + 1) as usize; + if end_in_last_chunk < chunk_size as usize { + let mut stmt = conn + .prepare_cached("SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?") + .await?; + let mut rows = stmt.query((ino, last_chunk_idx as i64)).await?; + + if let Some(row) = rows.next().await? { + if let Ok(Value::Blob(chunk_data)) = row.get_value(0) { + if chunk_data.len() > end_in_last_chunk { + conn.execute( + "UPDATE fs_data SET data = ? WHERE ino = ? AND chunk_index = ?", + (&chunk_data[..end_in_last_chunk], ino, last_chunk_idx as i64), + ) + .await?; + if let Some(hooks) = hooks { + hooks + .chunk_written(conn, ino, last_chunk_idx as i64) + .await?; + } + } + } + } + } + } else if new_size > current_size { + let last_existing_chunk = if current_size == 0 { + None + } else { + Some((current_size - 1) / chunk_size) + }; + let last_new_chunk = (new_size - 1) / chunk_size; + + if let Some(last_idx) = last_existing_chunk { + let mut stmt = conn + .prepare_cached("SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?") + .await?; + let mut rows = stmt.query((ino, last_idx as i64)).await?; + + if let Some(row) = rows.next().await? { + if let Ok(Value::Blob(chunk_data)) = row.get_value(0) { + let current_chunk_len = chunk_data.len(); + let needed_len = if last_idx == last_new_chunk { + ((new_size - 1) % chunk_size + 1) as usize + } else { + chunk_size as usize + }; + + if needed_len > current_chunk_len { + let mut padded = chunk_data.clone(); + padded.resize(needed_len, 0); + conn.execute( + "UPDATE fs_data SET data = ? WHERE ino = ? AND chunk_index = ?", + (&padded[..], ino, last_idx as i64), + ) + .await?; + if let Some(hooks) = hooks { + hooks.chunk_written(conn, ino, last_idx as i64).await?; + } + } + } + } + } + + let start_new_chunk = last_existing_chunk.map(|i| i + 1).unwrap_or(0); + for chunk_idx in start_new_chunk..=last_new_chunk { + let chunk_len = if chunk_idx == last_new_chunk { + ((new_size - 1) % chunk_size + 1) as usize + } else { + chunk_size as usize + }; + let zeros = vec![0u8; chunk_len]; + conn.execute( + "INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?)", + (ino, chunk_idx as i64, &zeros[..]), + ) + .await?; + if let Some(hooks) = hooks { + hooks.chunk_written(conn, ino, chunk_idx as i64).await?; + } + } + } + + Ok(()) +} + +async fn update_chunked_truncate_metadata( + conn: &Connection, + ino: i64, + new_size: u64, +) -> Result<()> { + let (now_secs, now_nsec) = current_timestamp()?; + conn.execute( + "UPDATE fs_inode SET size = ?, data_inline = NULL, storage_kind = ?, mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?", + ( + new_size as i64, + STORAGE_CHUNKED, + now_secs, + now_secs, + now_nsec, + now_nsec, + ino, + ), + ) + .await?; + Ok(()) +} + +async fn write_data_at_offset( + conn: &Connection, + ino: i64, + geometry: Geometry, + offset: u64, + data: &[u8], + hooks: Option<&dyn ChunkWriteHooks>, +) -> Result<()> { + let ranges = [WriteRangeRef { offset, data }]; + let ranges = normalize_write_ranges(&ranges)?; + write_ranges_chunked(conn, ino, geometry, &ranges, hooks).await +} + +async fn write_ranges_chunked( + conn: &Connection, + ino: i64, + geometry: Geometry, + ranges: &[NormalizedWriteRange], + hooks: Option<&dyn ChunkWriteHooks>, +) -> Result<()> { + let chunk_size = geometry.chunk_size as u64; + + if ranges.is_empty() { + return Ok(()); + } + + let mut select_stmt = conn + .prepare_cached("SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?") + .await?; + let mut insert_stmt = conn + .prepare_cached("INSERT OR REPLACE INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?)") + .await?; + + let mut chunks: BTreeMap> = BTreeMap::new(); + + for range in ranges { + let mut written = 0usize; + while written < range.data.len() { + let current_offset = range.offset + written as u64; + let chunk_index = (current_offset / chunk_size) as i64; + let offset_in_chunk = (current_offset % chunk_size) as usize; + + let remaining_in_chunk = geometry.chunk_size - offset_in_chunk; + let remaining_data = range.data.len() - written; + let to_write = std::cmp::min(remaining_in_chunk, remaining_data); + let write_slice = &range.data[written..written + to_write]; + + if offset_in_chunk == 0 && to_write == geometry.chunk_size { + chunks.insert(chunk_index, write_slice.to_vec()); + written += to_write; + continue; + } + + if let std::collections::btree_map::Entry::Vacant(entry) = chunks.entry(chunk_index) { + let mut rows = select_stmt.query((ino, chunk_index)).await?; + let existing_chunk = if let Some(row) = rows.next().await? { + Some(match row.get_value(0) { + Ok(Value::Blob(data)) => data, + Ok(_) | Err(_) => { + return Err(corrupt_column("fs_data.data", "expected blob")) + } + }) + } else { + None + }; + drop(rows); + select_stmt.reset()?; + let chunk_data = if let Some(existing_chunk) = existing_chunk { + existing_chunk + } else if let Some(hooks) = hooks { + hooks + .seed_missing_chunk(conn, ino, geometry, chunk_index) + .await? + .unwrap_or_default() + } else { + Vec::new() + }; + entry.insert(chunk_data); + } + + let chunk_data = chunks + .get_mut(&chunk_index) + .expect("chunk must be loaded before partial write"); + if chunk_data.len() < offset_in_chunk + to_write { + chunk_data.resize(offset_in_chunk + to_write, 0); + } + chunk_data[offset_in_chunk..offset_in_chunk + to_write].copy_from_slice(write_slice); + + written += to_write; + } + } + + let chunks_written = chunks.len() as u64; + // Tier Three Axis H investigation: tried a multi-row VALUES batch + // with up to 32 rows per execute() but measured slower wall-time in + // 5-iter runs, suggesting libSQL doesn't share the + // prepared-statement cost reduction across different VALUES + // arities or that the per-execute setup cost dwarfed any saved + // round-trips on our workload sizes. Reverted to the cached + // single-row prepared statement. + for (chunk_index, chunk_data) in chunks { + insert_stmt + .execute((ino, chunk_index, Value::Blob(chunk_data))) + .await?; + insert_stmt.reset()?; + if let Some(hooks) = hooks { + hooks.chunk_written(conn, ino, chunk_index).await?; + } + } + + crate::telemetry::record_chunk_write_chunks(chunks_written); + Ok(()) +} + +pub(super) async fn link_count(conn: &Connection, ino: i64) -> Result { + let mut stmt = conn + .prepare_cached("SELECT nlink FROM fs_inode WHERE ino = ?") + .await?; + let mut rows = stmt.query((ino,)).await?; + + if let Some(row) = rows.next().await? { + required_u32(&row, 0, "nlink") + } else { + Ok(0) + } +} + +pub(super) async fn mode(conn: &Connection, ino: i64) -> Result> { + let mut stmt = conn + .prepare_cached("SELECT mode FROM fs_inode WHERE ino = ?") + .await?; + let mut rows = stmt.query((ino,)).await?; + + if let Some(row) = rows.next().await? { + Ok(Some(required_u32(&row, 0, "mode")?)) + } else { + Ok(None) + } +} + +pub(super) async fn getattr(conn: &Connection, ino: i64) -> Result> { + let mut stmt = conn + .prepare_cached("SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime, rdev, atime_nsec, mtime_nsec, ctime_nsec FROM fs_inode WHERE ino = ?") + .await?; + let mut rows = stmt.query((ino,)).await?; + + if let Some(row) = rows.next().await? { + Ok(Some(stats_from_row(&row)?)) + } else { + Ok(None) + } +} + +pub(super) fn stats_from_row(row: &turso::Row) -> Result { + stats_from_row_at(row, 0) +} + +pub(super) fn stats_from_row_at(row: &turso::Row, start: usize) -> Result { + let size = required_i64(row, start + 5, "size")?; + if size < 0 { + return Err(corrupt_column("size", "negative file size")); + } + Ok(Stats { + ino: required_i64(row, start, "ino")?, + mode: required_u32(row, start + 1, "mode")?, + nlink: required_u32(row, start + 2, "nlink")?, + uid: required_u32(row, start + 3, "uid")?, + gid: required_u32(row, start + 4, "gid")?, + size, + atime: required_i64(row, start + 6, "atime")?, + mtime: required_i64(row, start + 7, "mtime")?, + ctime: required_i64(row, start + 8, "ctime")?, + atime_nsec: required_u32(row, start + 10, "atime_nsec")?, + mtime_nsec: required_u32(row, start + 11, "mtime_nsec")?, + ctime_nsec: required_u32(row, start + 12, "ctime_nsec")?, + rdev: required_u64(row, start + 9, "rdev")?, + }) +} + +fn required_i64(row: &turso::Row, index: usize, column: &str) -> Result { + row.get_value(index) + .ok() + .and_then(|value| value.as_integer().copied()) + .ok_or_else(|| corrupt_column(column, "expected integer")) +} + +fn required_u32(row: &turso::Row, index: usize, column: &str) -> Result { + let value = required_i64(row, index, column)?; + u32::try_from(value).map_err(|_| corrupt_column(column, "integer out of u32 range")) +} + +fn required_u64(row: &turso::Row, index: usize, column: &str) -> Result { + let value = required_i64(row, index, column)?; + u64::try_from(value).map_err(|_| corrupt_column(column, "negative integer")) +} + +fn corrupt_column(column: &str, reason: &str) -> Error { + Error::Fs(FsError::Corrupt(format!("invalid {column}: {reason}"))) +} diff --git a/crates/agentfs-core/src/fs/agentfs/tests.rs b/crates/agentfs-core/src/fs/agentfs/tests.rs new file mode 100644 index 00000000..64cdca68 --- /dev/null +++ b/crates/agentfs-core/src/fs/agentfs/tests.rs @@ -0,0 +1,4016 @@ + use super::*; + use std::time::Duration; + use tempfile::tempdir; + + // Turso 0.5.x reports SQLite's standard numeric value for NORMAL. + const TURSO_OBSERVED_SYNCHRONOUS_NORMAL: i64 = 1; + + async fn create_test_fs() -> Result<(AgentFS, tempfile::TempDir)> { + create_test_fs_with_config(CoreConfig::from_env()).await + } + + async fn create_test_fs_with_config( + config: CoreConfig, + ) -> Result<(AgentFS, tempfile::TempDir)> { + let dir = tempdir()?; + let db_path = dir.path().join("test.db"); + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await?; + let pool = + ConnectionPool::with_options(DatabaseType::Local(db), file_backed_connection_pool_options()); + let fs = AgentFS::from_pool_with_path_and_config(pool, Some(db_path), config).await?; + Ok((fs, dir)) + } + + fn test_config_with_long_batch_window() -> CoreConfig { + let mut config = CoreConfig::default(); + config.batcher.enabled = true; + config.batcher.window = Duration::from_secs(60); + config.batcher.inode_bytes = 1_048_576; + config.batcher.global_bytes = 64 * 1024 * 1024; + config + } + + #[tokio::test] + async fn core_config_batcher_enabled_flows_through_options() -> Result<()> { + let dir = tempdir()?; + + let mut disabled = CoreConfig::default(); + disabled.batcher.enabled = false; + let disabled_agent = crate::AgentFS::open( + crate::AgentFSOptions::with_path(dir.path().join("disabled.db").to_string_lossy()) + .with_core_config(disabled), + ) + .await?; + assert!( + disabled_agent.fs.write_batcher.is_none(), + "AgentFSOptions CoreConfig should be able to disable the write batcher" + ); + + let enabled_agent = crate::AgentFS::open( + crate::AgentFSOptions::with_path(dir.path().join("enabled.db").to_string_lossy()) + .with_core_config(test_config_with_long_batch_window()), + ) + .await?; + assert!( + enabled_agent.fs.write_batcher.is_some(), + "AgentFSOptions CoreConfig should be able to enable the write batcher" + ); + + Ok(()) + } + + async fn fs_inode_column_count(conn: &Connection, column_name: &str) -> Result { + let mut rows = conn.query("PRAGMA table_info(fs_inode)", ()).await?; + let mut count = 0; + + while let Some(row) = rows.next().await? { + let name: String = row.get(1)?; + if name == column_name { + count += 1; + } + } + + Ok(count) + } + + async fn index_exists(conn: &Connection, index_name: &str) -> Result { + let mut rows = conn + .query( + "SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?", + (index_name,), + ) + .await?; + Ok(rows.next().await?.is_some()) + } + + fn cached_attr(fs: &AgentFS, ino: i64) -> Option { + fs.attr_cache.get(ino) + } + + fn negative_cached(fs: &AgentFS, parent_ino: i64, name: &str) -> bool { + fs.negative_dentry_cache.contains(parent_ino, name) + } + + async fn parent_and_name_for_test(fs: &AgentFS, path: &str) -> Result<(i64, String)> { + let path = fs.normalize_path(path); + let components = fs.split_path(&path); + if components.is_empty() { + return Err(FsError::RootOperation.into()); + } + let parent_path = if components.len() == 1 { + "/".to_string() + } else { + format!("/{}", components[..components.len() - 1].join("/")) + }; + let parent_ino = fs + .resolve_path(&parent_path) + .await? + .ok_or(FsError::NotFound)?; + Ok((parent_ino, components.last().unwrap().clone())) + } + + async fn rename_path_via_trait(fs: &AgentFS, from: &str, to: &str) -> Result<()> { + let (oldparent_ino, oldname) = parent_and_name_for_test(fs, from).await?; + let (newparent_ino, newname) = parent_and_name_for_test(fs, to).await?; + FileSystem::rename(fs, oldparent_ino, &oldname, newparent_ino, &newname).await + } + + fn assert_normalized_ranges(actual: &[NormalizedWriteRange], expected: &[(u64, &[u8])]) { + assert_eq!(actual.len(), expected.len()); + for (range, (offset, data)) in actual.iter().zip(expected.iter()) { + assert_eq!(range.offset, *offset); + assert_eq!(range.data, *data); + } + } + + #[test] + fn store_characterization_range_normalization_merges_overlaps_in_order() -> Result<()> { + let ranges = [ + WriteRangeRef { + offset: 4, + data: b"CCCC", + }, + WriteRangeRef { + offset: 0, + data: b"aaaaaa", + }, + WriteRangeRef { + offset: 2, + data: b"ZZ", + }, + WriteRangeRef { + offset: 8, + data: b"!", + }, + WriteRangeRef { + offset: 12, + data: b"", + }, + ]; + + let normalized = normalize_write_ranges(&ranges)?; + assert_normalized_ranges(&normalized, &[(0, b"aaZZaaCC!")]); + Ok(()) + } + + #[test] + fn store_characterization_range_normalization_keeps_sparse_gaps() -> Result<()> { + let ranges = [ + WriteRangeRef { + offset: 0, + data: b"ab", + }, + WriteRangeRef { + offset: 5, + data: b"xy", + }, + ]; + + let normalized = normalize_write_ranges(&ranges)?; + assert_normalized_ranges(&normalized, &[(0, b"ab"), (5, b"xy")]); + assert!(!dense_after_inline_write_batch(0, 7, &normalized)); + + let mut bridged_refs: Vec<_> = normalized + .iter() + .map(|range| WriteRangeRef { + offset: range.offset, + data: range.data.as_slice(), + }) + .collect(); + bridged_refs.push(WriteRangeRef { + offset: 2, + data: b"345", + }); + + let bridged = normalize_write_ranges(&bridged_refs)?; + assert_normalized_ranges(&bridged, &[(0, b"ab345xy")]); + assert!(dense_after_inline_write_batch(0, 7, &bridged)); + Ok(()) + } + + #[test] + fn store_characterization_range_normalization_rejects_offset_overflow() { + let ranges = [WriteRangeRef { + offset: u64::MAX, + data: b"x", + }]; + + match normalize_write_ranges(&ranges) { + Ok(_) => panic!("overflowing write range should fail"), + Err(error) => assert!(matches!(error, Error::Internal(_))), + } + } + + fn assert_corrupt_error(error: Error, expected_column: &str) { + match error { + Error::Fs(FsError::Corrupt(message)) => assert!( + message.contains(expected_column), + "corruption message {message:?} should mention {expected_column:?}" + ), + other => panic!("expected corrupt row error, got {other:?}"), + } + } + + #[tokio::test] + async fn row_decode_corruption_returns_error() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (created, file) = fs + .create_file("/corrupt.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"hello").await?; + file.drain_writes().await?; + + let conn = fs.pool.get_connection().await?; + conn.execute( + "UPDATE fs_inode SET mode = ? WHERE ino = ?", + (Value::Text("not-an-integer".to_string()), created.ino), + ) + .await?; + fs.invalidate_attr(created.ino); + + assert_corrupt_error( + FileSystem::getattr(&fs, created.ino).await.unwrap_err(), + "mode", + ); + assert_corrupt_error( + FileSystem::lookup(&fs, ROOT_INO, "corrupt.txt") + .await + .unwrap_err(), + "mode", + ); + assert_corrupt_error( + FileSystem::readdir_plus(&fs, ROOT_INO).await.unwrap_err(), + "mode", + ); + + let corrupt_dir = FileSystem::mkdir(&fs, ROOT_INO, "corrupt-dir", 0o755, 0, 0).await?; + conn.execute( + "UPDATE fs_inode SET mode = ? WHERE ino = ?", + (Value::Text("not-an-integer".to_string()), corrupt_dir.ino), + ) + .await?; + fs.invalidate_attr(corrupt_dir.ino); + assert_corrupt_error( + FileSystem::readdir(&fs, corrupt_dir.ino).await.unwrap_err(), + "mode", + ); + + conn.execute( + "UPDATE fs_inode SET mode = ?, storage_kind = ? WHERE ino = ?", + ( + (S_IFREG | 0o644) as i64, + Value::Text("not-an-integer".to_string()), + created.ino, + ), + ) + .await?; + fs.invalidate_attr(created.ino); + + assert_corrupt_error(file.pread(0, 5).await.unwrap_err(), "storage_kind"); + Ok(()) + } + + #[tokio::test] + async fn readdir_plus_after_returns_bounded_cookie_pages() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let expected: Vec = (0..96).map(|index| format!("entry-{index:04}")).collect(); + for name in &expected { + FileSystem::create_file(&fs, ROOT_INO, name, DEFAULT_FILE_MODE, 0, 0).await?; + } + + let page_size = 7; + let mut cookie = 0; + let mut seen = Vec::new(); + let mut page_count = 0; + loop { + let page = FileSystem::readdir_plus_after(&fs, ROOT_INO, cookie, page_size) + .await? + .expect("root directory exists"); + page_count += 1; + assert!( + page.entries.len() <= page_size, + "page returned {} entries despite a limit of {page_size}", + page.entries.len() + ); + if !page.end { + assert!(!page.entries.is_empty(), "non-final page must progress"); + } + if let Some(last) = page.entries.last() { + cookie = last.cookie; + } + seen.extend(page.entries.into_iter().map(|entry| entry.name)); + if page.end { + break; + } + } + + assert!(page_count > 1, "test must exercise multiple pages"); + assert_eq!(seen, expected); + Ok(()) + } + + #[tokio::test] + async fn readdir_plus_after_uses_unique_dentry_cookies_for_hardlinks() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let original = + FileSystem::create_file(&fs, ROOT_INO, "entry-0000", DEFAULT_FILE_MODE, 0, 0) + .await? + .0; + FileSystem::link(&fs, original.ino, ROOT_INO, "entry-0001").await?; + FileSystem::create_file(&fs, ROOT_INO, "entry-0002", DEFAULT_FILE_MODE, 0, 0).await?; + + let first = FileSystem::readdir_plus_after(&fs, ROOT_INO, 0, 2) + .await? + .expect("root directory exists"); + assert_eq!( + first + .entries + .iter() + .map(|entry| entry.name.as_str()) + .collect::>(), + ["entry-0000", "entry-0001"] + ); + assert_eq!( + first.entries[0].stats.ino, first.entries[1].stats.ino, + "test must place two hardlinks to the same inode on one page" + ); + assert_ne!( + first.entries[0].cookie, first.entries[1].cookie, + "hardlinked dentries need distinct cookies even when fileid/inode matches" + ); + + let second = FileSystem::readdir_plus_after( + &fs, + ROOT_INO, + first.entries.last().expect("first page entry").cookie, + 2, + ) + .await? + .expect("root directory exists"); + assert_eq!( + second + .entries + .iter() + .map(|entry| entry.name.as_str()) + .collect::>(), + ["entry-0002"], + "resuming after a hardlink page boundary must not duplicate or skip entries" + ); + assert!(second.end); + Ok(()) + } + + #[tokio::test] + async fn readdir_plus_after_rejects_deleted_cookie() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + FileSystem::create_file(&fs, ROOT_INO, "entry-0000", DEFAULT_FILE_MODE, 0, 0).await?; + FileSystem::create_file(&fs, ROOT_INO, "entry-0001", DEFAULT_FILE_MODE, 0, 0).await?; + + let first = FileSystem::readdir_plus_after(&fs, ROOT_INO, 0, 1) + .await? + .expect("root directory exists"); + let stale_cookie = first.entries[0].cookie; + FileSystem::unlink(&fs, ROOT_INO, "entry-0000").await?; + + let err = FileSystem::readdir_plus_after(&fs, ROOT_INO, stale_cookie, 1) + .await + .expect_err("deleted directory cookie should be rejected"); + assert!( + matches!(err, crate::error::Error::Fs(FsError::BadCookie)), + "expected BadCookie, got {err:?}" + ); + Ok(()) + } + + #[tokio::test] + async fn readdir_plus_after_query_plan_uses_bounded_dentry_indexes() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + for index in 0..16 { + let name = format!("entry-{index:04}"); + FileSystem::create_file(&fs, ROOT_INO, &name, DEFAULT_FILE_MODE, 0, 0).await?; + } + let cookie = FileSystem::readdir_plus_after(&fs, ROOT_INO, 0, 8) + .await? + .expect("root directory exists") + .entries + .pop() + .expect("eighth entry exists") + .cookie; + + let conn = fs.pool.get_connection().await?; + let mut rows = conn + .query( + "EXPLAIN QUERY PLAN SELECT name FROM fs_dentry WHERE id = ? AND parent_ino = ?", + (cookie, ROOT_INO), + ) + .await?; + let mut cookie_plan = Vec::new(); + while let Some(row) = rows.next().await? { + let detail: String = row.get(3)?; + cookie_plan.push(detail); + } + println!("PLAN cookie lookup: {cookie_plan:?}"); + assert!( + cookie_plan + .iter() + .any(|detail| detail.contains("INTEGER PRIMARY KEY") + || detail.contains("rowid=?")), + "cookie lookup must use the dentry primary key, got {cookie_plan:?}" + ); + + let mut rows = conn + .query( + "EXPLAIN QUERY PLAN SELECT d.id, d.name, i.ino, i.mode, i.nlink, i.uid, i.gid, i.size, i.atime, i.mtime, i.ctime, i.rdev, i.atime_nsec, i.mtime_nsec, i.ctime_nsec + FROM fs_dentry d + JOIN fs_inode i ON d.ino = i.ino + WHERE d.parent_ino = ? AND d.name > ? + ORDER BY d.parent_ino, d.name + LIMIT ?", + (ROOT_INO, "entry-0007".to_string(), 8_i64), + ) + .await?; + let mut page_plan = Vec::new(); + while let Some(row) = rows.next().await? { + let detail: String = row.get(3)?; + page_plan.push(detail); + } + println!("PLAN bounded page: {page_plan:?}"); + assert!( + page_plan + .iter() + .any(|detail| detail.contains("parent_ino=? AND name>?")), + "page query must range-seek by parent+name, got {page_plan:?}" + ); + assert!( + !page_plan.iter().any(|detail| detail.contains("USE SORTER")), + "page query must not sort the full remaining directory, got {page_plan:?}" + ); + + let mut rows = conn + .query( + "EXPLAIN QUERY PLAN SELECT d.id, d.name, i.ino, i.mode, i.nlink, i.uid, i.gid, i.size, i.atime, i.mtime, i.ctime, i.rdev, i.atime_nsec, i.mtime_nsec, i.ctime_nsec + FROM fs_dentry d + JOIN fs_inode i ON d.ino = i.ino + WHERE d.parent_ino = ? + ORDER BY d.parent_ino, d.name + LIMIT ?", + (ROOT_INO, 8_i64), + ) + .await?; + let mut first_page_plan = Vec::new(); + while let Some(row) = rows.next().await? { + let detail: String = row.get(3)?; + first_page_plan.push(detail); + } + println!("PLAN first page: {first_page_plan:?}"); + assert!( + first_page_plan + .iter() + .any(|detail| detail.contains("parent_ino=?")), + "first page query must seek by parent, got {first_page_plan:?}" + ); + assert!( + !first_page_plan + .iter() + .any(|detail| detail.contains("USE SORTER")), + "first page query must not sort the full directory, got {first_page_plan:?}" + ); + + Ok(()) + } + + #[tokio::test] + async fn ensure_current_restores_readdir_cookie_index_for_current_databases() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let conn = fs.pool.get_connection().await?; + conn.execute("DROP INDEX idx_fs_dentry_parent_ino", ()) + .await?; + assert!(!index_exists(&conn, "idx_fs_dentry_parent_ino").await?); + + schema::ensure_current(&conn).await?; + + assert!(index_exists(&conn, "idx_fs_dentry_parent_ino").await?); + Ok(()) + } + + #[tokio::test] + async fn corrupt_mode_mutations_return_corrupt_without_namespace_changes() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + FileSystem::mkdir(&fs, ROOT_INO, "dir", 0o755, 0, 0).await?; + let dir = FileSystem::lookup(&fs, ROOT_INO, "dir").await?.unwrap(); + + let conn = fs.pool.get_connection().await?; + conn.execute( + "UPDATE fs_inode SET mode = ? WHERE ino = ?", + (Value::Text("not-an-integer".to_string()), dir.ino), + ) + .await?; + fs.invalidate_attr(dir.ino); + + assert_corrupt_error( + FileSystem::unlink(&fs, ROOT_INO, "dir").await.unwrap_err(), + "mode", + ); + assert!( + FileSystem::lookup(&fs, ROOT_INO, "dir").await.is_err(), + "the corrupt dentry must still be present after the failed unlink" + ); + + conn.execute( + "UPDATE fs_inode SET mode = ? WHERE ino = ?", + ((S_IFDIR | 0o755) as i64, dir.ino), + ) + .await?; + fs.invalidate_attr(dir.ino); + assert!(FileSystem::lookup(&fs, ROOT_INO, "dir").await?.is_some()); + Ok(()) + } + + #[tokio::test] + async fn corrupt_path_child_inode_returns_corrupt_and_is_not_cached() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + fs.mkdir("/parent", 0, 0).await?; + let parent = fs.stat("/parent").await?.unwrap(); + fs.mkdir("/parent/child", 0, 0).await?; + + let conn = fs.pool.get_connection().await?; + conn.execute( + "UPDATE fs_dentry SET ino = ? WHERE parent_ino = ? AND name = ?", + ( + Value::Text("not-an-integer".to_string()), + parent.ino, + "child", + ), + ) + .await?; + fs.invalidate_dentry(parent.ino, "child"); + + assert_corrupt_error(fs.stat("/parent/child").await.unwrap_err(), "ino"); + assert!( + fs.dentry_cache.get(parent.ino, "child").is_none(), + "corrupt child ino must not populate the dentry cache" + ); + Ok(()) + } + + #[tokio::test] + async fn corrupt_lookup_child_inode_returns_corrupt_and_is_not_negative_cached() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + FileSystem::mkdir(&fs, ROOT_INO, "parent", 0o755, 0, 0).await?; + fs.invalidate_dentry(ROOT_INO, "parent"); + + let conn = fs.pool.get_connection().await?; + conn.execute( + "UPDATE fs_dentry SET ino = ? WHERE parent_ino = ? AND name = ?", + ( + Value::Text("not-an-integer".to_string()), + ROOT_INO, + "parent", + ), + ) + .await?; + + assert_corrupt_error( + FileSystem::lookup(&fs, ROOT_INO, "parent") + .await + .unwrap_err(), + "ino", + ); + assert!( + fs.dentry_cache.get(ROOT_INO, "parent").is_none(), + "corrupt dentry ino must not populate the positive cache" + ); + assert!( + !fs.negative_dentry_cache.contains(ROOT_INO, "parent"), + "corrupt dentry ino must not be cached as a negative lookup" + ); + Ok(()) + } + + #[tokio::test] + async fn path_api_delegates_to_trait_semantics() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + fs.mkdir("/live", 42, 43).await?; + let live = fs.stat("/live").await?.unwrap(); + assert!(live.is_directory()); + assert_eq!((live.uid, live.gid), (42, 43)); + + let (created, file) = fs + .create_file("/live/pending.txt", DEFAULT_FILE_MODE, 7, 9) + .await?; + assert_eq!((created.uid, created.gid), (7, 9)); + file.pwrite(0, b"pending bytes").await?; + + let pending = fs.stat("/live/pending.txt").await?.unwrap(); + assert_eq!( + pending.size, 13, + "path stat must observe pending batched writes before drain" + ); + assert_eq!( + fs.read_file("/live/pending.txt").await?.unwrap(), + b"pending bytes" + ); + + let (doomed, doomed_file) = fs + .create_file("/live/doomed.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + doomed_file + .pwrite(0, b"discard these pending bytes") + .await?; + drop(doomed_file); + + fs.remove("/live/doomed.txt").await?; + fs.drain_all().await?; + assert!(fs.stat("/live/doomed.txt").await?.is_none()); + assert_eq!( + count_rows(&fs, "fs_data", doomed.ino).await?, + 0, + "path remove must discard pending writes for the deleted inode" + ); + + fs.remove("/live/pending.txt").await?; + fs.remove("/live").await?; + assert!(fs.stat("/live").await?.is_none()); + + let missing = fs.remove("/missing.txt").await.unwrap_err(); + assert!(matches!(missing, Error::Fs(FsError::NotFound))); + + fs.fsync().await?; + Ok(()) + } + + #[tokio::test] + async fn read_file_follows_terminal_symlink() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let (_, file) = fs + .create_file("/target.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"target contents").await?; + file.fsync().await?; + FileSystem::symlink(&fs, ROOT_INO, "target.link", "target.txt", 0, 0).await?; + + let link = FileSystem::lookup(&fs, ROOT_INO, "target.link") + .await? + .unwrap(); + assert!(link.is_symlink()); + assert_eq!( + fs.read_file("/target.link").await?.unwrap(), + b"target contents" + ); + + Ok(()) + } + + #[tokio::test] + async fn import_entries_builds_tree_with_correct_content_and_stats() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let big = vec![0xabu8; DEFAULT_INLINE_THRESHOLD + DEFAULT_CHUNK_SIZE + 17]; + let entries = [ + ImportEntry { + path: "sub".to_string(), + mode: S_IFDIR | 0o755, + data: Vec::new(), + }, + ImportEntry { + path: "sub/inner".to_string(), + mode: S_IFDIR | 0o755, + data: Vec::new(), + }, + ImportEntry { + path: "sub/small.txt".to_string(), + mode: S_IFREG | 0o644, + data: b"hello import".to_vec(), + }, + ImportEntry { + path: "sub/inner/big.bin".to_string(), + mode: S_IFREG | 0o755, + data: big.clone(), + }, + ImportEntry { + path: "sub/link".to_string(), + mode: S_IFLNK | 0o777, + data: b"small.txt".to_vec(), + }, + ]; + let opts = ImportOptions { + uid: 7, + gid: 9, + timestamp: (1_700_000_000, 123_456_789), + }; + let imported = fs.import_entries(ROOT_INO, &entries, &opts).await?; + assert_eq!(imported.len(), entries.len()); + + assert_eq!( + fs.read_file("/sub/small.txt").await?.unwrap(), + b"hello import" + ); + assert_eq!(fs.read_file("/sub/inner/big.bin").await?.unwrap(), big); + assert_eq!(fs.readlink("/sub/link").await?.unwrap(), "small.txt"); + + let small = fs.stat("/sub/small.txt").await?.unwrap(); + let reported = imported.iter().find(|e| e.path == "sub/small.txt").unwrap(); + assert_eq!(small.ino, reported.ino); + assert_eq!(small.size as u64, reported.size); + assert_eq!(small.mode, S_IFREG | 0o644); + assert_eq!((small.uid, small.gid), (7, 9)); + assert_eq!(small.mtime, 1_700_000_000); + assert_eq!(small.mtime_nsec, 123_456_789); + assert_eq!(small.ctime, 1_700_000_000); + + let big_stat = fs.stat("/sub/inner/big.bin").await?.unwrap(); + assert_eq!(big_stat.size as usize, big.len()); + assert_eq!(big_stat.mode, S_IFREG | 0o755); + + let sub = fs.stat("/sub").await?.unwrap(); + assert_eq!(sub.nlink, 3); // "." + parent link + inner + + // Duplicate import collides on the dentry UNIQUE constraint. + let dup = fs.import_entries(ROOT_INO, &entries[..1], &opts).await; + assert!(matches!(dup, Err(Error::Fs(FsError::AlreadyExists)))); + + Ok(()) + } + + #[tokio::test] + async fn import_session_preserves_tree_and_chunk_limits() -> Result<()> { + let mut config = CoreConfig::default(); + config.batcher.txn_max_inodes = 2; + config.batcher.txn_max_bytes = usize::MAX; + let (fs, _dir) = create_test_fs_with_config(config).await?; + + let big = vec![0xcdu8; DEFAULT_INLINE_THRESHOLD + DEFAULT_CHUNK_SIZE + 11]; + let entries = [ + ImportEntry { + path: "tree".to_string(), + mode: S_IFDIR | 0o755, + data: Vec::new(), + }, + ImportEntry { + path: "tree/nested".to_string(), + mode: S_IFDIR | 0o700, + data: Vec::new(), + }, + ImportEntry { + path: "tree/small.txt".to_string(), + mode: S_IFREG | 0o640, + data: b"small import".to_vec(), + }, + ImportEntry { + path: "tree/nested/big.bin".to_string(), + mode: S_IFREG | 0o600, + data: big.clone(), + }, + ImportEntry { + path: "tree/link".to_string(), + mode: S_IFLNK | 0o777, + data: b"nested/big.bin".to_vec(), + }, + ]; + let opts = ImportOptions { + uid: 111, + gid: 222, + timestamp: (1_800_000_000, 987_654_321), + }; + + let commit_start = fs.import_commit_sizes.lock().unwrap().len(); + let mut session = fs.begin_import(ROOT_INO, opts.clone()).await?; + session.import_chunk(&entries[..2]).await?; + session.import_chunk(&entries[2..]).await?; + let imported = session.finish(); + let commit_sizes = { + let sizes = fs.import_commit_sizes.lock().unwrap(); + sizes[commit_start..].to_vec() + }; + + assert_eq!(imported.len(), entries.len()); + assert_eq!( + commit_sizes, + vec![2, 2, 1], + "two explicit import chunks should commit as 2 + 2 + 1 inode transactions" + ); + println!( + "imported {} entries in transaction chunks {:?}", + imported.len(), + commit_sizes + ); + + let tree = fs.stat("/tree").await?.unwrap(); + assert_eq!(tree.mode, S_IFDIR | 0o755); + assert_eq!(tree.nlink, 3); + assert_eq!((tree.uid, tree.gid), (111, 222)); + assert_eq!(tree.mtime, opts.timestamp.0); + assert_eq!(tree.mtime_nsec, opts.timestamp.1 as u32); + + let small = fs.stat("/tree/small.txt").await?.unwrap(); + let small_reported = imported + .iter() + .find(|entry| entry.path == "tree/small.txt") + .unwrap(); + assert_eq!(small.ino, small_reported.ino); + assert_eq!(small.mode, S_IFREG | 0o640); + assert_eq!(small.size as u64, small_reported.size); + assert_eq!( + fs.read_file("/tree/small.txt").await?.unwrap(), + b"small import" + ); + + let big_stat = fs.stat("/tree/nested/big.bin").await?.unwrap(); + let big_reported = imported + .iter() + .find(|entry| entry.path == "tree/nested/big.bin") + .unwrap(); + assert_eq!(big_stat.ino, big_reported.ino); + assert_eq!(big_stat.mode, S_IFREG | 0o600); + assert_eq!(big_stat.size as usize, big.len()); + assert_eq!(fs.read_file("/tree/nested/big.bin").await?.unwrap(), big); + assert_eq!(fs.readlink("/tree/link").await?.unwrap(), "nested/big.bin"); + Ok(()) + } + + #[tokio::test] + async fn attr_cache_invalidates_mutations_and_preserves_visibility() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + FileSystem::getattr(&fs, ROOT_INO).await?.unwrap(); + assert!(cached_attr(&fs, ROOT_INO).is_some()); + + let (created, file) = + FileSystem::create_file(&fs, ROOT_INO, "cache.txt", DEFAULT_FILE_MODE, 7, 9).await?; + let file_ino = created.ino; + assert!(cached_attr(&fs, ROOT_INO).is_none()); + assert_eq!(cached_attr(&fs, file_ino).unwrap().size, 0); + + file.pwrite(0, b"hello").await?; + assert!(cached_attr(&fs, file_ino).is_none()); + let written = FileSystem::getattr(&fs, file_ino).await?.unwrap(); + assert_eq!(written.size, 5); + assert_eq!(cached_attr(&fs, file_ino).unwrap().size, 5); + + file.pwrite(5, b" world").await?; + let after_append = FileSystem::getattr(&fs, file_ino).await?.unwrap(); + assert_eq!(after_append.size, 11); + assert_eq!(file.pread(0, 11).await?, b"hello world"); + + file.truncate(5).await?; + assert!(cached_attr(&fs, file_ino).is_none()); + let truncated = FileSystem::getattr(&fs, file_ino).await?.unwrap(); + assert_eq!(truncated.size, 5); + assert_eq!(file.pread(0, 16).await?, b"hello"); + + FileSystem::chmod(&fs, file_ino, 0o600).await?; + assert!(cached_attr(&fs, file_ino).is_none()); + let chmodded = FileSystem::getattr(&fs, file_ino).await?.unwrap(); + assert_eq!(chmodded.mode & 0o7777, 0o600); + + FileSystem::chown(&fs, file_ino, Some(11), Some(13)).await?; + assert!(cached_attr(&fs, file_ino).is_none()); + let chowned = FileSystem::getattr(&fs, file_ino).await?.unwrap(); + assert_eq!((chowned.uid, chowned.gid), (11, 13)); + + FileSystem::utimens( + &fs, + file_ino, + TimeChange::Set(1_700_000_001, 123), + TimeChange::Set(1_700_000_002, 456), + ) + .await?; + assert!(cached_attr(&fs, file_ino).is_none()); + let timestamped = FileSystem::getattr(&fs, file_ino).await?.unwrap(); + assert_eq!( + (timestamped.mtime, timestamped.mtime_nsec), + (1_700_000_002, 456) + ); + + FileSystem::getattr(&fs, ROOT_INO).await?.unwrap(); + let linked = FileSystem::link(&fs, file_ino, ROOT_INO, "hard.txt").await?; + assert!(cached_attr(&fs, ROOT_INO).is_none()); + assert_eq!(linked.nlink, 2); + assert_eq!( + FileSystem::lookup(&fs, ROOT_INO, "hard.txt") + .await? + .unwrap() + .ino, + file_ino + ); + + FileSystem::getattr(&fs, ROOT_INO).await?.unwrap(); + let symlink = FileSystem::symlink(&fs, ROOT_INO, "cache.link", "cache.txt", 11, 13).await?; + assert!(cached_attr(&fs, ROOT_INO).is_none()); + assert!(symlink.is_symlink()); + assert_eq!( + FileSystem::readlink(&fs, symlink.ino).await?, + Some("cache.txt".to_string()) + ); + + FileSystem::getattr(&fs, ROOT_INO).await?.unwrap(); + let dir = FileSystem::mkdir(&fs, ROOT_INO, "dir", 0o755, 11, 13).await?; + assert!(cached_attr(&fs, ROOT_INO).is_none()); + assert!(cached_attr(&fs, dir.ino).is_some()); + FileSystem::getattr(&fs, ROOT_INO).await?.unwrap(); + FileSystem::rmdir(&fs, ROOT_INO, "dir").await?; + assert!(cached_attr(&fs, ROOT_INO).is_none()); + assert!(cached_attr(&fs, dir.ino).is_none()); + assert!(FileSystem::lookup(&fs, ROOT_INO, "dir").await?.is_none()); + + FileSystem::getattr(&fs, file_ino).await?.unwrap(); + FileSystem::getattr(&fs, ROOT_INO).await?.unwrap(); + FileSystem::rename(&fs, ROOT_INO, "cache.txt", ROOT_INO, "renamed.txt").await?; + assert!(cached_attr(&fs, file_ino).is_none()); + assert!(cached_attr(&fs, ROOT_INO).is_none()); + assert!(FileSystem::lookup(&fs, ROOT_INO, "cache.txt") + .await? + .is_none()); + assert_eq!( + FileSystem::lookup(&fs, ROOT_INO, "renamed.txt") + .await? + .unwrap() + .ino, + file_ino + ); + assert_eq!(file.pread(0, 16).await?, b"hello"); + + FileSystem::getattr(&fs, file_ino).await?.unwrap(); + FileSystem::unlink(&fs, ROOT_INO, "hard.txt").await?; + assert!(cached_attr(&fs, file_ino).is_none()); + let single_link = FileSystem::getattr(&fs, file_ino).await?.unwrap(); + assert_eq!(single_link.nlink, 1); + + FileSystem::unlink(&fs, ROOT_INO, "renamed.txt").await?; + assert!(cached_attr(&fs, file_ino).is_none()); + assert!(FileSystem::lookup(&fs, ROOT_INO, "renamed.txt") + .await? + .is_none()); + + Ok(()) + } + + #[tokio::test] + async fn negative_dentry_cache_invalidates_on_namespace_mutations() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + assert!(FileSystem::lookup(&fs, ROOT_INO, "missing.txt") + .await? + .is_none()); + assert!(negative_cached(&fs, ROOT_INO, "missing.txt")); + + let (created, _file) = + FileSystem::create_file(&fs, ROOT_INO, "missing.txt", DEFAULT_FILE_MODE, 7, 9).await?; + assert!(!negative_cached(&fs, ROOT_INO, "missing.txt")); + assert_eq!( + FileSystem::lookup(&fs, ROOT_INO, "missing.txt") + .await? + .unwrap() + .ino, + created.ino + ); + + FileSystem::rename(&fs, ROOT_INO, "missing.txt", ROOT_INO, "renamed.txt").await?; + assert!(negative_cached(&fs, ROOT_INO, "missing.txt")); + assert!(!negative_cached(&fs, ROOT_INO, "renamed.txt")); + assert!(FileSystem::lookup(&fs, ROOT_INO, "missing.txt") + .await? + .is_none()); + assert_eq!( + FileSystem::lookup(&fs, ROOT_INO, "renamed.txt") + .await? + .unwrap() + .ino, + created.ino + ); + + FileSystem::unlink(&fs, ROOT_INO, "renamed.txt").await?; + assert!(negative_cached(&fs, ROOT_INO, "renamed.txt")); + assert!(FileSystem::lookup(&fs, ROOT_INO, "renamed.txt") + .await? + .is_none()); + + assert!(FileSystem::lookup(&fs, ROOT_INO, "negdir").await?.is_none()); + assert!(negative_cached(&fs, ROOT_INO, "negdir")); + FileSystem::mkdir(&fs, ROOT_INO, "negdir", 0o755, 7, 9).await?; + assert!(!negative_cached(&fs, ROOT_INO, "negdir")); + FileSystem::rmdir(&fs, ROOT_INO, "negdir").await?; + assert!(negative_cached(&fs, ROOT_INO, "negdir")); + + Ok(()) + } + + async fn read_pragma_i64(conn: &Connection, sql: &str) -> i64 { + let mut rows = conn.query(sql, ()).await.unwrap(); + let row = rows.next().await.unwrap().unwrap(); + row.get_value(0) + .ok() + .and_then(|value| match value { + Value::Integer(value) => Some(value), + Value::Text(value) => value.parse().ok(), + _ => None, + }) + .unwrap() + } + + async fn read_pragma_text(conn: &Connection, sql: &str) -> String { + let mut rows = conn.query(sql, ()).await.unwrap(); + let row = rows.next().await.unwrap().unwrap(); + row.get_value(0) + .ok() + .and_then(|value| match value { + Value::Text(value) => Some(value.clone()), + Value::Integer(value) => Some(value.to_string()), + _ => None, + }) + .unwrap() + } + + // ==================== Chunk Size Boundary Tests ==================== + + #[tokio::test] + async fn test_file_smaller_than_chunk_size() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Write a file smaller than chunk_size (100 bytes) + let data = vec![0u8; 100]; + let (_, file) = fs + .create_file("/small.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + + // Read it back + let read_data = fs.read_file("/small.txt").await?.unwrap(); + assert_eq!(read_data.len(), 100); + assert_eq!(read_data, data); + + // Verify inline storage avoids chunks + let ino = fs.resolve_path("/small.txt").await?.unwrap(); + let chunk_count = fs.get_chunk_count(ino).await?; + assert_eq!(chunk_count, 0); + let (storage_kind, data_inline) = fs.get_storage_state(ino).await?; + assert_eq!(storage_kind, STORAGE_INLINE); + assert_eq!(data_inline, Some(data)); + + Ok(()) + } + + #[tokio::test] + async fn test_file_exactly_chunk_size() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Write exactly chunk_size bytes + let chunk_size = fs.chunk_size(); + let data: Vec = (0..chunk_size).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs + .create_file("/exact.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + + // Read it back + let read_data = fs.read_file("/exact.txt").await?.unwrap(); + assert_eq!(read_data.len(), chunk_size); + assert_eq!(read_data, data); + + // Verify only 1 chunk was created + let ino = fs.resolve_path("/exact.txt").await?.unwrap(); + let chunk_count = fs.get_chunk_count(ino).await?; + assert_eq!(chunk_count, 1); + + Ok(()) + } + + #[tokio::test] + async fn test_file_one_byte_over_chunk_size() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Write chunk_size + 1 bytes + let chunk_size = fs.chunk_size(); + let data: Vec = (0..=chunk_size).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs + .create_file("/overflow.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + + // Read it back + let read_data = fs.read_file("/overflow.txt").await?.unwrap(); + assert_eq!(read_data.len(), chunk_size + 1); + assert_eq!(read_data, data); + + // Verify 2 chunks were created + let ino = fs.resolve_path("/overflow.txt").await?.unwrap(); + let chunk_count = fs.get_chunk_count(ino).await?; + assert_eq!(chunk_count, 2); + + Ok(()) + } + + #[tokio::test] + async fn test_file_spanning_multiple_chunks() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Write ~2.5 chunks worth of data + let chunk_size = fs.chunk_size(); + let data_size = chunk_size * 2 + chunk_size / 2; + let data: Vec = (0..data_size).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs + .create_file("/multi.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + + // Read it back + let read_data = fs.read_file("/multi.txt").await?.unwrap(); + assert_eq!(read_data.len(), data_size); + assert_eq!(read_data, data); + + // Verify 3 chunks were created + let ino = fs.resolve_path("/multi.txt").await?.unwrap(); + let chunk_count = fs.get_chunk_count(ino).await?; + assert_eq!(chunk_count, 3); + + Ok(()) + } + + // ==================== Data Integrity Tests ==================== + + #[tokio::test] + async fn test_roundtrip_byte_for_byte() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Create data that spans chunk boundaries with identifiable patterns + let chunk_size = fs.chunk_size(); + let data_size = chunk_size * 3 + 123; // Odd size spanning 4 chunks + + let data: Vec = (0..data_size).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs + .create_file("/roundtrip.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + + let read_data = fs.read_file("/roundtrip.bin").await?.unwrap(); + assert_eq!(read_data.len(), data_size); + assert_eq!(read_data, data, "Data mismatch after roundtrip"); + + Ok(()) + } + + #[tokio::test] + async fn test_binary_data_with_null_bytes() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let chunk_size = fs.chunk_size(); + // Create data with null bytes at chunk boundaries + let mut data = vec![0u8; chunk_size * 2 + 100]; + // Put nulls at the chunk boundary + data[chunk_size - 1] = 0; + data[chunk_size] = 0; + data[chunk_size + 1] = 0; + // Put some non-null bytes around + data[chunk_size - 2] = 0xFF; + data[chunk_size + 2] = 0xFF; + + let (_, file) = fs + .create_file("/nulls.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + let read_data = fs.read_file("/nulls.bin").await?.unwrap(); + + assert_eq!(read_data, data, "Null bytes at chunk boundary corrupted"); + + Ok(()) + } + + #[tokio::test] + async fn test_chunk_ordering() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let chunk_size = fs.chunk_size(); + // Create sequential bytes spanning multiple chunks + let data_size = chunk_size * 5; + let data: Vec = (0..data_size).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs + .create_file("/sequential.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + + let read_data = fs.read_file("/sequential.bin").await?.unwrap(); + + // Verify every byte is in the correct position + for (i, (&expected, &actual)) in data.iter().zip(read_data.iter()).enumerate() { + assert_eq!( + expected, actual, + "Byte mismatch at position {}: expected {}, got {}", + i, expected, actual + ); + } + + Ok(()) + } + + // ==================== Edge Case Tests ==================== + + #[tokio::test] + async fn test_empty_file() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Write empty file + let (_, file) = fs + .create_file("/empty.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &[]).await?; + + // Read it back + let read_data = fs.read_file("/empty.txt").await?.unwrap(); + assert!(read_data.is_empty()); + + // Verify 0 chunks were created + let ino = fs.resolve_path("/empty.txt").await?.unwrap(); + let chunk_count = fs.get_chunk_count(ino).await?; + assert_eq!(chunk_count, 0); + + // Verify size is 0 + let stats = fs.stat("/empty.txt").await?.unwrap(); + assert_eq!(stats.size, 0); + + let (storage_kind, data_inline) = fs.get_storage_state(ino).await?; + assert_eq!(storage_kind, STORAGE_INLINE); + assert_eq!(data_inline, Some(Vec::new())); + + Ok(()) + } + + #[tokio::test] + async fn test_inline_small_file_and_overwrite() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let (_, file) = fs + .create_file("/inline.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"hello world").await?; + file.pwrite(6, b"agent").await?; + + let ino = fs.resolve_path("/inline.txt").await?.unwrap(); + assert_eq!(fs.read_file("/inline.txt").await?.unwrap(), b"hello agent"); + assert_eq!(fs.get_chunk_count(ino).await?, 0); + let (storage_kind, data_inline) = fs.get_storage_state(ino).await?; + assert_eq!(storage_kind, STORAGE_INLINE); + assert_eq!(data_inline, Some(b"hello agent".to_vec())); + + Ok(()) + } + + #[tokio::test] + async fn test_inline_transitions_to_chunked_over_threshold() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let prefix = vec![1u8; DEFAULT_INLINE_THRESHOLD]; + let suffix = vec![2u8; 32]; + let (_, file) = fs + .create_file("/transition.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &prefix).await?; + + let ino = fs.resolve_path("/transition.bin").await?.unwrap(); + assert_eq!(fs.get_storage_state(ino).await?.0, STORAGE_INLINE); + + file.pwrite(DEFAULT_INLINE_THRESHOLD as u64, &suffix) + .await?; + + let mut expected = prefix; + expected.extend_from_slice(&suffix); + assert_eq!(fs.read_file("/transition.bin").await?.unwrap(), expected); + assert_eq!(fs.get_storage_state(ino).await?, (STORAGE_CHUNKED, None)); + assert_eq!(fs.get_chunk_count(ino).await?, 1); + + Ok(()) + } + + #[tokio::test] + async fn test_sparse_write_transitions_inline_to_chunked() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let (_, file) = fs + .create_file("/sparse.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"abc").await?; + file.pwrite(10, b"z").await?; + + let ino = fs.resolve_path("/sparse.bin").await?.unwrap(); + assert_eq!(fs.get_storage_state(ino).await?, (STORAGE_CHUNKED, None)); + assert_eq!(fs.get_chunk_count(ino).await?, 1); + + let mut expected = b"abc".to_vec(); + expected.resize(10, 0); + expected.push(b'z'); + let read_back = file.pread(0, expected.len() as u64).await?; + assert_eq!(read_back, expected); + + Ok(()) + } + + #[tokio::test] + async fn test_chunked_truncate_back_to_inline_when_dense() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let data = vec![7u8; DEFAULT_INLINE_THRESHOLD + 1]; + let (_, file) = fs + .create_file("/dense.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + + let ino = fs.resolve_path("/dense.bin").await?.unwrap(); + assert_eq!(fs.get_storage_state(ino).await?, (STORAGE_CHUNKED, None)); + + file.truncate(128).await?; + + assert_eq!(fs.read_file("/dense.bin").await?.unwrap(), vec![7u8; 128]); + assert_eq!(fs.get_chunk_count(ino).await?, 0); + let (storage_kind, data_inline) = fs.get_storage_state(ino).await?; + assert_eq!(storage_kind, STORAGE_INLINE); + assert_eq!(data_inline, Some(vec![7u8; 128])); + + Ok(()) + } + + #[tokio::test] + async fn test_sparse_chunked_truncate_below_threshold_stays_chunked() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let (_, file) = fs + .create_file("/sparse-truncate.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(fs.chunk_size() as u64 + 8, b"tail").await?; + // Tier Four: ensure the sparse write reaches SQLite as chunked + // storage before we truncate; otherwise truncate_pending strips it + // in memory and the file never transitions out of INLINE. + file.fsync().await?; + file.truncate(4).await?; + + let ino = fs.resolve_path("/sparse-truncate.bin").await?.unwrap(); + assert_eq!(fs.get_storage_state(ino).await?, (STORAGE_CHUNKED, None)); + assert_eq!(file.pread(0, 4).await?, vec![0u8; 4]); + + Ok(()) + } + + #[tokio::test] + async fn test_64k_chunk_boundary_uses_single_default_chunk() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + assert_eq!(fs.chunk_size(), 64 * 1024); + let data: Vec = (0..fs.chunk_size()).map(|i| (i % 251) as u8).collect(); + let (_, file) = fs + .create_file("/boundary.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + + let ino = fs.resolve_path("/boundary.bin").await?.unwrap(); + assert_eq!(fs.get_storage_state(ino).await?, (STORAGE_CHUNKED, None)); + assert_eq!(fs.get_chunk_count(ino).await?, 1); + assert_eq!( + file.pread((fs.chunk_size() - 8) as u64, 16).await?, + data[fs.chunk_size() - 8..].to_vec() + ); + + Ok(()) + } + + #[tokio::test] + async fn test_overwrite_existing_file() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let chunk_size = fs.chunk_size(); + + // Write initial large file (3 chunks) + let initial_data: Vec = (0..chunk_size * 3).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs + .create_file("/overwrite.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &initial_data).await?; + + let ino = fs.resolve_path("/overwrite.txt").await?.unwrap(); + let initial_chunk_count = fs.get_chunk_count(ino).await?; + assert_eq!(initial_chunk_count, 3); + + // Overwrite with smaller file (1 chunk) + let new_data = vec![42u8; 100]; + file.truncate(0).await?; + file.pwrite(0, &new_data).await?; + + // Verify old chunks are gone and new data is correct + let read_data = fs.read_file("/overwrite.txt").await?.unwrap(); + assert_eq!(read_data, new_data); + + let new_chunk_count = fs.get_chunk_count(ino).await?; + assert_eq!(new_chunk_count, 0); + let (storage_kind, data_inline) = fs.get_storage_state(ino).await?; + assert_eq!(storage_kind, STORAGE_INLINE); + assert_eq!(data_inline, Some(new_data)); + + // Verify size is updated + let stats = fs.stat("/overwrite.txt").await?.unwrap(); + assert_eq!(stats.size, 100); + + Ok(()) + } + + #[tokio::test] + async fn test_overwrite_with_larger_file() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let chunk_size = fs.chunk_size(); + + // Write initial small file (1 chunk) + let initial_data = vec![1u8; 100]; + let (_, file) = fs.create_file("/grow.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &initial_data).await?; + + let ino = fs.resolve_path("/grow.txt").await?.unwrap(); + assert_eq!(fs.get_chunk_count(ino).await?, 0); + assert_eq!(fs.get_storage_state(ino).await?.0, STORAGE_INLINE); + + // Overwrite with larger file (3 chunks) + let new_data: Vec = (0..chunk_size * 3).map(|i| (i % 256) as u8).collect(); + file.truncate(0).await?; + file.pwrite(0, &new_data).await?; + + // Verify data is correct + let read_data = fs.read_file("/grow.txt").await?.unwrap(); + assert_eq!(read_data, new_data); + assert_eq!(fs.get_chunk_count(ino).await?, 3); + + Ok(()) + } + + #[tokio::test] + async fn test_very_large_file() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Write 1MB file + let data_size = 1024 * 1024; + let data: Vec = (0..data_size).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs + .create_file("/large.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + + let read_data = fs.read_file("/large.bin").await?.unwrap(); + assert_eq!(read_data.len(), data_size); + assert_eq!(read_data, data); + + // Verify correct number of chunks + let chunk_size = fs.chunk_size(); + let expected_chunks = data_size.div_ceil(chunk_size); + let ino = fs.resolve_path("/large.bin").await?.unwrap(); + let actual_chunks = fs.get_chunk_count(ino).await? as usize; + assert_eq!(actual_chunks, expected_chunks); + + Ok(()) + } + + // ==================== Configuration Tests ==================== + + #[tokio::test] + async fn test_default_chunk_size() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + assert_eq!(fs.chunk_size(), DEFAULT_CHUNK_SIZE); + assert_eq!(fs.chunk_size(), 65536); + assert_eq!(fs.inline_threshold(), DEFAULT_INLINE_THRESHOLD); + assert_eq!(fs.inline_threshold(), 16384); + + Ok(()) + } + + #[tokio::test] + async fn test_chunk_size_accessor() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let chunk_size = fs.chunk_size(); + assert!(chunk_size > 0); + + // Write data and verify chunks match expected based on chunk_size + let data = vec![0u8; chunk_size * 2 + 1]; + let (_, file) = fs.create_file("/test.bin", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + + let ino = fs.resolve_path("/test.bin").await?.unwrap(); + let chunk_count = fs.get_chunk_count(ino).await?; + assert_eq!(chunk_count, 3); + + Ok(()) + } + + #[tokio::test] + async fn test_config_persistence() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Query fs_config table directly + let conn = fs.pool.get_connection().await?; + let mut rows = conn + .query("SELECT value FROM fs_config WHERE key = 'chunk_size'", ()) + .await?; + + let row = rows.next().await?.expect("chunk_size config should exist"); + let value = row + .get_value(0) + .ok() + .and_then(|v| match v { + Value::Text(s) => Some(s.clone()), + _ => None, + }) + .expect("chunk_size should be a text value"); + + assert_eq!(value, "65536"); + + let mut rows = conn + .query( + "SELECT value FROM fs_config WHERE key = 'inline_threshold'", + (), + ) + .await?; + let row = rows + .next() + .await? + .expect("inline_threshold config should exist"); + let value = row + .get_value(0) + .ok() + .and_then(|v| match v { + Value::Text(s) => Some(s.clone()), + _ => None, + }) + .expect("inline_threshold should be a text value"); + + assert_eq!(value, "16384"); + + let mut rows = conn + .query( + "SELECT value FROM fs_config WHERE key = ?", + (schema::CONFIG_SCHEMA_VERSION_KEY,), + ) + .await?; + let row = rows + .next() + .await? + .expect("schema version config should exist"); + let value = row + .get_value(0) + .ok() + .and_then(|v| match v { + Value::Text(s) => Some(s.clone()), + _ => None, + }) + .expect("schema version should be a text value"); + + assert_eq!(value, "0.5"); + + Ok(()) + } + + #[tokio::test] + async fn schema_alter_non_duplicate_errors_propagate() -> Result<()> { + let dir = tempdir()?; + let db_path = dir.path().join("malformed-view.db"); + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + + conn.execute("CREATE VIEW fs_inode AS SELECT 1 AS ino", ()) + .await?; + + let err = match schema::ensure_current(&conn).await { + Ok(_) => panic!("non-duplicate schema DDL errors must propagate"), + Err(err) => err, + }; + let err_msg = err.to_string(); + assert!( + err_msg.contains("fs_inode"), + "error should preserve the failed DDL target, got: {err_msg}" + ); + + Ok(()) + } + + #[tokio::test] + async fn schema_alter_conflicting_column_definition_is_rejected() -> Result<()> { + let dir = tempdir()?; + let db_path = dir.path().join("malformed-conflicting-column.db"); + + { + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + + conn.execute( + "CREATE TABLE fs_config (key TEXT PRIMARY KEY, value TEXT NOT NULL)", + (), + ) + .await?; + conn.execute( + "INSERT INTO fs_config (key, value) VALUES + (?, '0.5'), + (?, '16384')", + ( + schema::CONFIG_SCHEMA_VERSION_KEY, + schema::CONFIG_INLINE_THRESHOLD_KEY, + ), + ) + .await?; + conn.execute( + "CREATE TABLE fs_inode ( + ino INTEGER PRIMARY KEY, + mode INTEGER NOT NULL, + nlink INTEGER NOT NULL DEFAULT 0, + uid INTEGER NOT NULL DEFAULT 0, + gid INTEGER NOT NULL DEFAULT 0, + size INTEGER NOT NULL DEFAULT 0, + atime INTEGER NOT NULL, + mtime INTEGER NOT NULL, + ctime INTEGER NOT NULL, + rdev INTEGER NOT NULL DEFAULT 0, + atime_nsec TEXT NOT NULL DEFAULT 'bad', + mtime_nsec INTEGER NOT NULL DEFAULT 0, + ctime_nsec INTEGER NOT NULL DEFAULT 0, + data_inline BLOB, + storage_kind INTEGER NOT NULL DEFAULT 0 + )", + (), + ) + .await?; + conn.execute( + "INSERT INTO fs_inode + (ino, mode, nlink, uid, gid, size, atime, mtime, ctime, rdev, + atime_nsec, mtime_nsec, ctime_nsec, storage_kind) + VALUES (?, ?, 2, 0, 0, 0, 1, 1, 1, 0, 'bad', 0, 0, 0)", + (ROOT_INO, DEFAULT_DIR_MODE as i64), + ) + .await?; + } + + let err = match AgentFS::new(db_path.to_str().unwrap()).await { + Ok(_) => panic!("opening a database with a conflicting schema column must fail"), + Err(err) => err, + }; + let err_msg = err.to_string(); + assert!( + err_msg.contains("fs_inode.atime_nsec") && err_msg.contains("incompatible definition"), + "error should name the incompatible schema column, got: {err_msg}" + ); + + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + let mut rows = conn.query("PRAGMA table_info(fs_inode)", ()).await?; + let mut found_conflicting_column = false; + while let Some(row) = rows.next().await? { + let name: String = row.get(1)?; + if name == "atime_nsec" { + let type_name: String = row.get(2)?; + assert_eq!(type_name, "TEXT"); + found_conflicting_column = true; + } + } + assert!(found_conflicting_column); + + Ok(()) + } + + #[tokio::test] + async fn schema_alter_duplicate_columns_are_idempotent_on_reopen() -> Result<()> { + let dir = tempdir()?; + let db_path = dir.path().join("already-upgraded.db"); + + let first = AgentFS::new(db_path.to_str().unwrap()).await?; + drop(first); + + let reopened = AgentFS::new(db_path.to_str().unwrap()).await?; + let conn = reopened.pool.get_connection().await?; + + for column_name in [ + "atime_nsec", + "mtime_nsec", + "ctime_nsec", + "data_inline", + "storage_kind", + ] { + assert_eq!( + fs_inode_column_count(&conn, column_name).await?, + 1, + "fs_inode should contain exactly one {column_name} column" + ); + } + + let mut rows = conn + .query( + "SELECT value FROM fs_config WHERE key = ?", + (schema::CONFIG_SCHEMA_VERSION_KEY,), + ) + .await?; + let version: String = rows + .next() + .await? + .expect("schema version config should exist") + .get(0)?; + assert_eq!(version, schema::AGENTFS_SCHEMA_VERSION); + + Ok(()) + } + + #[tokio::test] + async fn test_v04_database_is_rejected_on_open_until_migrated() -> Result<()> { + let dir = tempdir()?; + let db_path = dir.path().join("legacy-v04.db"); + + { + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + conn.execute( + "CREATE TABLE fs_config (key TEXT PRIMARY KEY, value TEXT NOT NULL)", + (), + ) + .await?; + conn.execute( + "INSERT INTO fs_config (key, value) VALUES (?, '0.4')", + (schema::CONFIG_SCHEMA_VERSION_KEY,), + ) + .await?; + conn.execute( + "CREATE TABLE fs_inode ( + ino INTEGER PRIMARY KEY AUTOINCREMENT, + mode INTEGER NOT NULL, + nlink INTEGER NOT NULL DEFAULT 0, + uid INTEGER NOT NULL DEFAULT 0, + gid INTEGER NOT NULL DEFAULT 0, + size INTEGER NOT NULL DEFAULT 0, + atime INTEGER NOT NULL, + mtime INTEGER NOT NULL, + ctime INTEGER NOT NULL, + rdev INTEGER NOT NULL DEFAULT 0, + atime_nsec INTEGER NOT NULL DEFAULT 0, + mtime_nsec INTEGER NOT NULL DEFAULT 0, + ctime_nsec INTEGER NOT NULL DEFAULT 0 + )", + (), + ) + .await?; + } + + let err = + match crate::AgentFS::open(crate::AgentFSOptions::with_path(db_path.to_string_lossy())) + .await + { + Ok(_) => panic!("open must not upgrade a v0.4 schema"), + Err(err) => err, + }; + assert!(matches!(err, Error::SchemaVersionMismatch { .. }), "{err}"); + + { + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + assert_eq!( + schema::detect_schema_version(&conn).await?, + Some(schema::SchemaVersion::V0_4) + ); + schema::ensure_current(&conn).await?; + } + + let agent = + crate::AgentFS::open(crate::AgentFSOptions::with_path(db_path.to_string_lossy())) + .await?; + let conn = agent.get_connection().await?; + assert_eq!( + schema::detect_schema_version(&conn).await?, + Some(schema::CURRENT) + ); + + Ok(()) + } + + #[tokio::test] + async fn test_file_backed_connections_use_production_pragmas() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let conn1 = fs.pool.get_connection().await?; + let conn2 = fs.pool.get_connection().await?; + + for conn in [&conn1, &conn2] { + assert_eq!( + read_pragma_i64(conn, "PRAGMA synchronous").await, + TURSO_OBSERVED_SYNCHRONOUS_NORMAL + ); + assert_eq!(read_pragma_i64(conn, "PRAGMA busy_timeout").await, 5000); + assert_eq!(read_pragma_i64(conn, "PRAGMA temp_store").await, 2); + assert_eq!( + read_pragma_text(conn, "PRAGMA journal_mode") + .await + .to_lowercase(), + "wal" + ); + } + + Ok(()) + } + + #[tokio::test] + async fn test_file_backed_options_issue_durable_baseline_sql() { + let options = file_backed_connection_pool_options(); + + assert_eq!(options.max_connections, FILE_BACKED_MAX_CONNECTIONS); + assert_eq!(options.setup_sql[0], TEMP_STORE_MEMORY_SQL); + assert!(options.setup_sql.iter().any(|sql| sql == BUSY_TIMEOUT_SQL)); + assert!(options.setup_sql.iter().any(|sql| sql == WAL_MODE_SQL)); + assert!(options + .setup_sql + .iter() + .any(|sql| sql == BASELINE_SYNCHRONOUS_SQL)); + assert!(!options + .setup_sql + .iter() + .any(|sql| sql == "PRAGMA synchronous = OFF")); + } + + #[tokio::test] + async fn test_memory_agentfs_connections_use_temp_store_memory() -> Result<()> { + let agentfs = crate::AgentFS::open(crate::AgentFSOptions::ephemeral()).await?; + + let conn = agentfs.get_connection().await?; + assert_eq!(read_pragma_i64(&conn, "PRAGMA temp_store").await, 2); + drop(conn); + + let core_agentfs = AgentFS::new(":memory:").await?; + let core_conn = core_agentfs.pool.get_connection().await?; + assert_eq!(read_pragma_i64(&core_conn, "PRAGMA temp_store").await, 2); + + Ok(()) + } + + #[tokio::test] + async fn test_file_backed_agentfs_concurrent_operations_complete() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs.create_file("/seed.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, b"seed").await?; + + let mut handles = Vec::new(); + for worker in 0..8 { + let fs = fs.clone(); + handles.push(tokio::spawn(async move { + for iteration in 0..5 { + let data = fs.read_file("/seed.txt").await?.unwrap(); + assert_eq!(data, b"seed"); + + let path = format!("/worker-{worker}-{iteration}"); + fs.mkdir(&path, 0, 0).await?; + } + Ok::<(), Error>(()) + })); + } + + for handle in handles { + handle.await.unwrap()?; + } + + Ok(()) + } + + #[tokio::test] + async fn test_fsync_restores_synchronous_normal() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let conn = fs.pool.get_connection().await?; + conn.execute("PRAGMA synchronous = OFF", ()).await?; + drop(conn); + + fs.fsync().await?; + + let conn = fs.pool.get_connection().await?; + assert_eq!( + read_pragma_i64(&conn, "PRAGMA synchronous").await, + TURSO_OBSERVED_SYNCHRONOUS_NORMAL + ); + + Ok(()) + } + + #[tokio::test] + async fn test_file_fsync_restores_synchronous_normal() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs + .create_file("/fsync.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + + let conn = fs.pool.get_connection().await?; + conn.execute("PRAGMA synchronous = OFF", ()).await?; + drop(conn); + + file.fsync().await?; + + let conn = fs.pool.get_connection().await?; + assert_eq!( + read_pragma_i64(&conn, "PRAGMA synchronous").await, + TURSO_OBSERVED_SYNCHRONOUS_NORMAL + ); + + Ok(()) + } + + // ==================== Schema Tests ==================== + + #[tokio::test] + async fn test_chunk_index_uniqueness() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Write a file to create chunks + let chunk_size = fs.chunk_size(); + let data = vec![0u8; chunk_size * 2]; + let (_, file) = fs + .create_file("/unique.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + + let ino = fs.resolve_path("/unique.txt").await?.unwrap(); + // Tier Four: pwrite is async-batched; drain so fs_data is populated + // before we probe its primary-key constraint. + fs.drain_inode_writes(ino).await?; + + // Try to insert a duplicate chunk - should fail due to PRIMARY KEY constraint + let conn = fs.pool.get_connection().await?; + let result = conn + .execute( + "INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, 0, ?)", + (ino, vec![1u8; 10]), + ) + .await; + + assert!(result.is_err(), "Duplicate chunk_index should be rejected"); + + Ok(()) + } + + #[tokio::test] + async fn test_chunk_ordering_in_database() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let chunk_size = fs.chunk_size(); + // Create 5 chunks with identifiable data + let data_size = chunk_size * 5; + let data: Vec = (0..data_size).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs + .create_file("/ordered.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + + let ino = fs.resolve_path("/ordered.bin").await?.unwrap(); + // Tier Four: drain so fs_data rows are present for the SELECT below. + fs.drain_inode_writes(ino).await?; + + // Query chunks in order + let conn = fs.pool.get_connection().await?; + let mut rows = conn + .query( + "SELECT chunk_index FROM fs_data WHERE ino = ? ORDER BY chunk_index", + (ino,), + ) + .await?; + + let mut indices = Vec::new(); + while let Some(row) = rows.next().await? { + let idx = row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(-1); + indices.push(idx); + } + + assert_eq!(indices, vec![0, 1, 2, 3, 4]); + + Ok(()) + } + + // ==================== Cleanup Tests ==================== + + #[tokio::test] + async fn test_delete_file_removes_all_chunks() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let chunk_size = fs.chunk_size(); + // Create multi-chunk file + let data = vec![0u8; chunk_size * 4]; + let (_, file) = fs + .create_file("/deleteme.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &data).await?; + + let ino = fs.resolve_path("/deleteme.txt").await?.unwrap(); + assert_eq!(fs.get_chunk_count(ino).await?, 4); + + // Close the handle first: with it open, deletion is deferred (POSIX + // unlink-while-open) and the chunks legitimately survive the remove. + drop(file); + + // Delete the file + fs.remove("/deleteme.txt").await?; + + // Verify all chunks are gone + let conn = fs.pool.get_connection().await?; + let mut rows = conn + .query("SELECT COUNT(*) FROM fs_data WHERE ino = ?", (ino,)) + .await?; + + let count = rows + .next() + .await? + .and_then(|r| r.get_value(0).ok().and_then(|v| v.as_integer().copied())) + .unwrap_or(-1); + + assert_eq!(count, 0, "All chunks should be deleted"); + + Ok(()) + } + + async fn count_rows(fs: &AgentFS, table: &str, ino: i64) -> Result { + let conn = fs.pool.get_connection().await?; + let mut rows = conn + .query( + &format!("SELECT COUNT(*) FROM {table} WHERE ino = ?"), + (ino,), + ) + .await?; + Ok(rows + .next() + .await? + .and_then(|r| r.get_value(0).ok().and_then(|v| v.as_integer().copied())) + .unwrap_or(-1)) + } + + #[tokio::test] + async fn test_unlink_while_open_defers_reap() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let (stats, file) = fs + .create_file("/ghost.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + let ino = stats.ino; + file.pwrite(0, b"ghost").await?; + + FileSystem::unlink(&fs, ROOT_INO, "ghost.bin").await?; + + // POSIX: the open handle keeps the inode readable and writable. + assert!(fs.resolve_path("/ghost.bin").await?.is_none()); + assert_eq!(file.pread(0, 5).await?, b"ghost"); + file.pwrite(5, b"-more").await?; + assert_eq!(file.pread(0, 10).await?, b"ghost-more"); + assert_eq!(file.fstat().await?.nlink, 0); + assert_eq!(count_rows(&fs, "fs_inode", ino).await?, 1); + + // Last handle drop queues the reap; the next namespace mutation + // (or finalize) executes it. + drop(file); + fs.process_deferred_reaps().await?; + assert_eq!(count_rows(&fs, "fs_inode", ino).await?, 0); + assert_eq!(count_rows(&fs, "fs_data", ino).await?, 0); + + Ok(()) + } + + #[derive(Default)] + struct FailOnceReapHook { + failed: std::sync::atomic::AtomicBool, + } + + #[async_trait::async_trait] + impl ReapHook for FailOnceReapHook { + async fn on_reap(&self, conn: &Connection, ino: i64) -> Result<()> { + conn.execute("INSERT INTO reap_hook_probe (ino) VALUES (?)", (ino,)) + .await?; + if !self.failed.swap(true, std::sync::atomic::Ordering::SeqCst) { + return Err(Error::Internal("intentional reap hook failure".to_string())); + } + Ok(()) + } + } + + struct PendingStateReapHook { + batcher: Arc, + saw_pending: std::sync::atomic::AtomicBool, + } + + #[async_trait::async_trait] + impl ReapHook for PendingStateReapHook { + async fn on_reap(&self, _conn: &Connection, ino: i64) -> Result<()> { + self.saw_pending.store( + self.batcher.has_pending(ino), + std::sync::atomic::Ordering::SeqCst, + ); + Ok(()) + } + } + + async fn count_probe_rows(fs: &AgentFS, ino: i64) -> Result { + let conn = fs.pool.get_connection().await?; + let mut rows = conn + .query("SELECT COUNT(*) FROM reap_hook_probe WHERE ino = ?", (ino,)) + .await?; + Ok(rows + .next() + .await? + .and_then(|r| r.get_value(0).ok().and_then(|v| v.as_integer().copied())) + .unwrap_or(-1)) + } + + #[tokio::test] + async fn lifecycle_reap_hook_fires_atomically() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let conn = fs.pool.get_connection().await?; + conn.execute("CREATE TABLE reap_hook_probe (ino INTEGER PRIMARY KEY)", ()) + .await?; + drop(conn); + + fs.register_reap_hook(Arc::new(FailOnceReapHook::default())); + + let (stats, file) = fs + .create_file("/hooked.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + let ino = stats.ino; + file.pwrite(0, b"hooked").await?; + + FileSystem::unlink(&fs, ROOT_INO, "hooked.bin").await?; + assert_eq!(file.pread(0, 6).await?, b"hooked"); + drop(file); + + let err = fs + .process_deferred_reaps() + .await + .expect_err("first hook invocation should fail"); + assert!( + err.to_string().contains("intentional reap hook failure"), + "unexpected reap hook error: {err}" + ); + assert_eq!( + count_rows(&fs, "fs_inode", ino).await?, + 1, + "failed hook must roll back the inode deletion" + ); + assert_eq!( + count_probe_rows(&fs, ino).await?, + 0, + "hook writes must be in the same transaction as the reap" + ); + + fs.process_deferred_reaps().await?; + assert_eq!(count_probe_rows(&fs, ino).await?, 1); + assert_eq!(count_rows(&fs, "fs_inode", ino).await?, 0); + assert_eq!(count_rows(&fs, "fs_data", ino).await?, 0); + + Ok(()) + } + + #[tokio::test] + async fn inline_unlink_reap_rollback_keeps_pending_writes_until_commit() -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let conn = fs.pool.get_connection().await?; + conn.execute("CREATE TABLE reap_hook_probe (ino INTEGER PRIMARY KEY)", ()) + .await?; + drop(conn); + + fs.register_reap_hook(Arc::new(FailOnceReapHook::default())); + + let (stats, file) = fs + .create_file("/rollback-unlink.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + let ino = stats.ino; + file.pwrite(0, b"rollback-pending").await?; + drop(file); + + let batcher = fs + .write_batcher + .as_ref() + .expect("long-window test config enables the batcher") + .clone(); + assert!( + batcher.has_pending(ino), + "test setup requires a pending write before inline reap" + ); + + let err = FileSystem::unlink(&fs, ROOT_INO, "rollback-unlink.bin") + .await + .expect_err("first inline reap hook invocation should fail"); + assert!( + err.to_string().contains("intentional reap hook failure"), + "unexpected reap hook error: {err}" + ); + assert_eq!( + count_rows(&fs, "fs_inode", ino).await?, + 1, + "failed inline reap must roll back the inode deletion" + ); + assert_eq!( + count_probe_rows(&fs, ino).await?, + 0, + "hook writes must roll back with the failed inline reap" + ); + assert!( + batcher.has_pending(ino), + "pending writes must survive while the metadata transaction rolls back" + ); + assert_eq!( + fs.read_file("/rollback-unlink.bin").await?.unwrap(), + b"rollback-pending" + ); + + FileSystem::unlink(&fs, ROOT_INO, "rollback-unlink.bin").await?; + assert!( + !batcher.has_pending(ino), + "pending writes are discarded only after the inline reap commits" + ); + assert_eq!(count_probe_rows(&fs, ino).await?, 1); + assert_eq!(count_rows(&fs, "fs_inode", ino).await?, 0); + assert_eq!(count_rows(&fs, "fs_data", ino).await?, 0); + + Ok(()) + } + + #[tokio::test] + async fn inline_rename_replace_reap_rollback_keeps_pending_writes_until_commit() -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let conn = fs.pool.get_connection().await?; + conn.execute("CREATE TABLE reap_hook_probe (ino INTEGER PRIMARY KEY)", ()) + .await?; + drop(conn); + + fs.register_reap_hook(Arc::new(FailOnceReapHook::default())); + + let (victim, victim_file) = fs + .create_file("/victim.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + victim_file.pwrite(0, b"victim-pending").await?; + drop(victim_file); + let (replacement, replacement_file) = fs + .create_file("/replacement.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + replacement_file.pwrite(0, b"replacement").await?; + drop(replacement_file); + + let batcher = fs + .write_batcher + .as_ref() + .expect("long-window test config enables the batcher") + .clone(); + assert!( + batcher.has_pending(victim.ino), + "test setup requires pending writes on the replaced inode" + ); + + let err = FileSystem::rename(&fs, ROOT_INO, "replacement.bin", ROOT_INO, "victim.bin") + .await + .expect_err("first rename-replace reap hook invocation should fail"); + assert!( + err.to_string().contains("intentional reap hook failure"), + "unexpected reap hook error: {err}" + ); + assert!( + batcher.has_pending(victim.ino), + "pending writes for the replaced inode must survive rollback" + ); + assert_eq!( + fs.read_file("/victim.bin").await?.unwrap(), + b"victim-pending" + ); + assert_eq!( + FileSystem::lookup(&fs, ROOT_INO, "replacement.bin") + .await? + .expect("source dentry should roll back") + .ino, + replacement.ino + ); + assert_eq!(count_probe_rows(&fs, victim.ino).await?, 0); + + FileSystem::rename(&fs, ROOT_INO, "replacement.bin", ROOT_INO, "victim.bin").await?; + assert!( + !batcher.has_pending(victim.ino), + "pending writes for the replaced inode are discarded after commit" + ); + assert_eq!( + FileSystem::lookup(&fs, ROOT_INO, "victim.bin") + .await? + .expect("replacement should land at destination") + .ino, + replacement.ino + ); + assert!( + FileSystem::lookup(&fs, ROOT_INO, "replacement.bin") + .await? + .is_none() + ); + assert_eq!(count_probe_rows(&fs, victim.ino).await?, 1); + assert_eq!(count_rows(&fs, "fs_inode", victim.ino).await?, 0); + assert_eq!(count_rows(&fs, "fs_data", victim.ino).await?, 0); + + Ok(()) + } + + #[tokio::test] + async fn deferred_reap_discards_pending_before_reap_hooks() -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let (stats, file) = fs + .create_file("/pending-reap.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + let ino = stats.ino; + file.pwrite(0, b"pending reap").await?; + + let batcher = fs + .write_batcher + .as_ref() + .expect("long-window test config enables the batcher") + .clone(); + assert!( + batcher.has_pending(ino), + "test setup requires a pending write before the file is reaped" + ); + + let hook = Arc::new(PendingStateReapHook { + batcher: Arc::clone(&batcher), + saw_pending: std::sync::atomic::AtomicBool::new(true), + }); + fs.register_reap_hook(hook.clone()); + + FileSystem::unlink(&fs, ROOT_INO, "pending-reap.bin").await?; + drop(file); + fs.process_deferred_reaps().await?; + + assert!( + !hook.saw_pending.load(std::sync::atomic::Ordering::SeqCst), + "pending writes must be discarded before reap hooks run in the reap transaction" + ); + assert!( + !batcher.has_pending(ino), + "pending writes for a reaped inode must not survive the reap" + ); + assert_eq!(count_rows(&fs, "fs_inode", ino).await?, 0); + assert_eq!(count_rows(&fs, "fs_data", ino).await?, 0); + + Ok(()) + } + + #[tokio::test] + async fn test_mount_sweep_reaps_crashed_orphans() -> Result<()> { + let dir = tempfile::tempdir()?; + let db_path = dir.path().join("sweep.db"); + let db_path = db_path.to_str().unwrap(); + + let ino = { + let fs = AgentFS::new(db_path).await?; + let (stats, file) = fs + .create_file("/ghost.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"ghost").await?; + file.drain_writes().await?; + FileSystem::unlink(&fs, ROOT_INO, "ghost.bin").await?; + // Simulate a crash: the guard never releases, so the orphan is + // neither queued nor reaped before the process "dies". + std::mem::forget(file); + stats.ino + }; + + let fs = AgentFS::new(db_path).await?; + assert_eq!(count_rows(&fs, "fs_inode", ino).await?, 0); + assert_eq!(count_rows(&fs, "fs_data", ino).await?, 0); + + Ok(()) + } + + #[tokio::test] + async fn lifecycle_reaps_open_unlink_and_mount_sweep() -> Result<()> { + let dir = tempfile::tempdir()?; + let db_path = dir.path().join("lifecycle.db"); + let db_path = db_path.to_str().unwrap(); + + let deferred_ino = { + let fs = AgentFS::new(db_path).await?; + let (stats, file) = fs + .create_file("/deferred.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"deferred").await?; + FileSystem::unlink(&fs, ROOT_INO, "deferred.bin").await?; + assert!(fs.resolve_path("/deferred.bin").await?.is_none()); + assert_eq!(file.pread(0, 8).await?, b"deferred"); + assert_eq!(file.fstat().await?.nlink, 0); + drop(file); + fs.process_deferred_reaps().await?; + stats.ino + }; + + let crashed_ino = { + let fs = AgentFS::new(db_path).await?; + let (stats, file) = fs + .create_file("/crashed.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"crashed").await?; + file.drain_writes().await?; + FileSystem::unlink(&fs, ROOT_INO, "crashed.bin").await?; + std::mem::forget(file); + stats.ino + }; + + let fs = AgentFS::new(db_path).await?; + for ino in [deferred_ino, crashed_ino] { + assert_eq!( + count_rows(&fs, "fs_inode", ino).await?, + 0, + "reaped inode {ino} should not remain in fs_inode" + ); + assert_eq!( + count_rows(&fs, "fs_data", ino).await?, + 0, + "reaped inode {ino} should not leave fs_data rows" + ); + assert_eq!( + count_rows(&fs, "fs_symlink", ino).await?, + 0, + "reaped inode {ino} should not leave fs_symlink rows" + ); + } + + let conn = fs.pool.get_connection().await?; + let mut rows = conn + .query("SELECT COUNT(*) FROM fs_inode WHERE nlink = 0", ()) + .await?; + let nlink_zero = rows + .next() + .await? + .and_then(|r| r.get_value(0).ok().and_then(|v| v.as_integer().copied())) + .unwrap_or(-1); + assert_eq!(nlink_zero, 0, "mount sweep should leave no nlink=0 rows"); + + Ok(()) + } + + #[tokio::test] + async fn test_multiple_files_different_sizes() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let chunk_size = fs.chunk_size(); + + // Create files of various sizes + let files = vec![ + ("/tiny.txt", 10), + ("/small.txt", chunk_size / 2), + ("/exact.txt", chunk_size), + ("/medium.txt", chunk_size * 2 + 100), + ("/large.txt", chunk_size * 5), + ]; + + for (path, size) in &files { + let data: Vec = (0..*size).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs.create_file(path, DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + } + + // Verify each file has correct data and chunk count + for (path, size) in &files { + let read_data = fs.read_file(path).await?.unwrap(); + assert_eq!(read_data.len(), *size, "Size mismatch for {}", path); + + let expected_data: Vec = (0..*size).map(|i| (i % 256) as u8).collect(); + assert_eq!(read_data, expected_data, "Data mismatch for {}", path); + + let expected_chunks = if *size <= fs.inline_threshold() { + 0 + } else { + size.div_ceil(chunk_size) + }; + let ino = fs.resolve_path(path).await?.unwrap(); + let actual_chunks = fs.get_chunk_count(ino).await? as usize; + assert_eq!( + actual_chunks, expected_chunks, + "Chunk count mismatch for {}", + path + ); + } + + Ok(()) + } + + #[tokio::test] + async fn file_pread_basic() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let data: Vec = (0..100).collect(); + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + + assert_eq!(file.pread(0, 10).await?, &data[0..10]); + assert_eq!(file.pread(50, 20).await?, &data[50..70]); + assert_eq!(file.pread(90, 10).await?, &data[90..100]); + Ok(()) + } + + #[tokio::test] + async fn file_pread_past_eof() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let data: Vec = (0..50).collect(); + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + + assert!(file.pread(100, 10).await?.is_empty()); + assert_eq!(file.pread(40, 20).await?, &data[40..50]); + Ok(()) + } + + #[tokio::test] + async fn file_open_nonexistent_fails() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let result = fs.open("/nonexistent.txt").await; + assert!(matches!(result, Err(Error::Fs(FsError::NotFound)))); + Ok(()) + } + + #[tokio::test] + async fn file_pread_across_chunks() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let chunk_size = fs.chunk_size(); + let data: Vec = (0..(chunk_size * 3)).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + + let start = chunk_size - 10; + assert_eq!( + file.pread(start as u64, 20).await?, + &data[start..start + 20] + ); + + let start = chunk_size / 2; + let size = chunk_size * 2; + assert_eq!( + file.pread(start as u64, size as u64).await?, + &data[start..start + size] + ); + Ok(()) + } + + #[tokio::test] + async fn file_pwrite_basic() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let data = vec![0; 100]; + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + file.pwrite(50, &[1, 2, 3, 4, 5]).await?; + + let result = fs.read_file("/test.txt").await?.unwrap(); + assert_eq!(result.len(), 100); + assert_eq!(&result[50..55], &[1, 2, 3, 4, 5]); + assert_eq!(&result[0..50], &vec![0u8; 50][..]); + assert_eq!(&result[55..100], &vec![0u8; 45][..]); + Ok(()) + } + + #[tokio::test] + async fn file_pwrite_extend_file() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let data = vec![1; 50]; + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + file.pwrite(100, &[2, 2, 2, 2, 2]).await?; + + let result = fs.read_file("/test.txt").await?.unwrap(); + assert_eq!(result.len(), 105); + assert_eq!(&result[0..50], &vec![1u8; 50][..]); + assert_eq!(&result[50..100], &vec![0u8; 50][..]); + assert_eq!(&result[100..105], &[2, 2, 2, 2, 2]); + Ok(()) + } + + #[tokio::test] + async fn file_create_then_pwrite_writes_file() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs.create_file("/new.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &[1, 2, 3]).await?; + + assert_eq!(fs.read_file("/new.txt").await?.unwrap(), &[1, 2, 3]); + Ok(()) + } + + #[tokio::test] + async fn file_pwrite_across_chunks() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let chunk_size = fs.chunk_size(); + let data = vec![0; chunk_size * 3]; + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + + let write_data: Vec = (0..20).collect(); + let start = chunk_size - 10; + file.pwrite(start as u64, &write_data).await?; + + let result = fs.read_file("/test.txt").await?.unwrap(); + assert_eq!(&result[start..start + 20], &write_data[..]); + assert_eq!(&result[0..start], &vec![0u8; start][..]); + assert_eq!( + &result[start + 20..], + &vec![0u8; chunk_size * 3 - start - 20][..] + ); + Ok(()) + } + + #[tokio::test] + async fn file_pread_pwrite_roundtrip() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let chunk_size = fs.chunk_size(); + let initial: Vec = (0..(chunk_size * 2)).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &initial).await?; + + let patches = [ + (0u64, vec![0xAAu8; 10]), + (chunk_size as u64 - 5, vec![0xBB; 10]), + (chunk_size as u64 * 2 - 1, vec![0xCC; 1]), + ]; + + for (offset, data) in &patches { + file.pwrite(*offset, data).await?; + } + for (offset, expected) in &patches { + assert_eq!( + file.pread(*offset, expected.len() as u64).await?, + expected.as_slice() + ); + } + Ok(()) + } + + #[tokio::test] + async fn test_pwrite_ranges_preserves_order_and_inline_storage() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let (_, file) = fs + .create_file("/batch-inline.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite_ranges(vec![ + WriteRange { + offset: 0, + data: b"abcdef".to_vec(), + }, + WriteRange { + offset: 2, + data: b"ZZ".to_vec(), + }, + WriteRange { + offset: 6, + data: b"!".to_vec(), + }, + ]) + .await?; + + let ino = fs.resolve_path("/batch-inline.txt").await?.unwrap(); + assert_eq!(file.pread(0, 16).await?, b"abZZef!"); + assert_eq!(fs.get_chunk_count(ino).await?, 0); + assert_eq!( + fs.get_storage_state(ino).await?, + (STORAGE_INLINE, Some(b"abZZef!".to_vec())) + ); + + Ok(()) + } + + #[tokio::test] + async fn test_pwrite_ranges_disjoint_inplace_writes_stay_inline() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let initial: Vec = (0..128).collect(); + let (_, file) = fs + .create_file("/batch-inplace.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, &initial).await?; + + file.pwrite_ranges(vec![ + WriteRange { + offset: 8, + data: b"ABCD".to_vec(), + }, + WriteRange { + offset: 64, + data: b"WXYZ".to_vec(), + }, + ]) + .await?; + + let mut expected = initial; + expected[8..12].copy_from_slice(b"ABCD"); + expected[64..68].copy_from_slice(b"WXYZ"); + + let ino = fs.resolve_path("/batch-inplace.bin").await?.unwrap(); + assert_eq!(file.pread(0, expected.len() as u64).await?, expected); + assert_eq!(fs.get_chunk_count(ino).await?, 0); + assert_eq!(fs.get_storage_state(ino).await?.0, STORAGE_INLINE); + + Ok(()) + } + + #[tokio::test] + async fn test_pwrite_ranges_sparse_write_transitions_to_chunked() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + let (_, file) = fs + .create_file("/batch-sparse.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite_ranges(vec![ + WriteRange { + offset: 0, + data: b"head".to_vec(), + }, + WriteRange { + offset: fs.chunk_size() as u64 + 4, + data: b"tail".to_vec(), + }, + ]) + .await?; + + let ino = fs.resolve_path("/batch-sparse.bin").await?.unwrap(); + assert_eq!(fs.get_storage_state(ino).await?, (STORAGE_CHUNKED, None)); + assert_eq!(fs.get_chunk_count(ino).await?, 2); + + let mut expected = b"head".to_vec(); + expected.resize(fs.chunk_size() + 4, 0); + expected.extend_from_slice(b"tail"); + assert_eq!(file.pread(0, expected.len() as u64).await?, expected); + + Ok(()) + } + + #[tokio::test] + async fn test_pwrite_ranges_batched_drains_explicitly() -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let (stats, file) = fs + .create_file("/batched.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + + file.pwrite_ranges_batched(vec![ + WriteRange { + offset: 0, + data: b"hello".to_vec(), + }, + WriteRange { + offset: 5, + data: b" world".to_vec(), + }, + ]) + .await?; + + let flushed_stats = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); + assert_eq!( + flushed_stats.size, 11, + "metadata reads should drain pending batched writes before reporting size" + ); + + file.drain_writes().await?; + assert_eq!(file.pread(0, 32).await?, b"hello world"); + + Ok(()) + } + + #[tokio::test] + async fn test_setattr_after_batched_write_preserves_explicit_times() -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let (stats, file) = fs + .create_file("/setattr-after-write.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + + // Buffered write stays in the overlay (long timer, no drain). + file.pwrite_ranges_batched(vec![WriteRange { + offset: 0, + data: b"deferred body".to_vec(), + }]) + .await?; + + // Explicit setattr (the kernel's writeback mtime update) lands while + // the data is still pending. No drain happens here by default. + let explicit_secs = 1_234_567_890; + let explicit_nsec = 42; + FileSystem::utimens( + &fs, + stats.ino, + TimeChange::Omit, + TimeChange::Set(explicit_secs, explicit_nsec), + ) + .await?; + + // The deferred commit must NOT re-stamp mtime/ctime over the explicit + // value the setattr just wrote. + file.drain_writes().await?; + + let after = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); + assert_eq!( + after.mtime, explicit_secs, + "explicit mtime must survive the deferred data commit" + ); + assert_eq!( + after.mtime_nsec, explicit_nsec, + "explicit mtime_nsec must survive the deferred data commit" + ); + assert_eq!(after.size, 13); + assert_eq!(file.pread(0, 32).await?, b"deferred body"); + + Ok(()) + } + + #[tokio::test] + async fn test_write_after_setattr_restamps_times_on_commit() -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let (stats, file) = fs + .create_file("/write-after-setattr.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + + file.pwrite_ranges_batched(vec![WriteRange { + offset: 0, + data: b"first".to_vec(), + }]) + .await?; + + let stale_secs = 1_111_111_111; + FileSystem::utimens( + &fs, + stats.ino, + TimeChange::Omit, + TimeChange::Set(stale_secs, 0), + ) + .await?; + + // A write AFTER the setattr means the file changed again: the commit + // must stamp fresh mtime/ctime, not preserve the stale explicit value. + file.pwrite_ranges_batched(vec![WriteRange { + offset: 5, + data: b" second".to_vec(), + }]) + .await?; + + file.drain_writes().await?; + + let after = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); + assert!( + after.mtime > stale_secs, + "a write after the explicit setattr must bump mtime again (got {}, explicit was {})", + after.mtime, + stale_secs + ); + assert_eq!(file.pread(0, 32).await?, b"first second"); + + Ok(()) + } + + #[tokio::test] + async fn test_utimens_with_pending_writes_is_visible_and_committed_with_data() -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let (stats, file) = fs + .create_file("/stash-times.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + + // Buffered write stays in the overlay (long timer, no drain). + file.pwrite_ranges_batched(vec![WriteRange { + offset: 0, + data: b"stash body".to_vec(), + }]) + .await?; + + // The explicit setattr is stashed in the pending entry instead of + // paying its own SQLite transaction. + let explicit_secs = 1_999_999_999; + let explicit_nsec: u32 = 7; + FileSystem::utimens( + &fs, + stats.ino, + TimeChange::Set(11, 13), + TimeChange::Set(explicit_secs, explicit_nsec), + ) + .await?; + + // Visible immediately, before any drain commits the row UPDATE. + let before = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); + assert_eq!( + before.mtime, explicit_secs, + "stashed mtime must be visible before the drain commits it" + ); + assert_eq!(before.mtime_nsec, explicit_nsec); + assert_eq!(before.atime, 11); + assert_eq!(before.atime_nsec, 13); + assert_eq!(before.size, 10, "pending data size must still be merged"); + + // The drain commits the data and the stashed times in one transaction. + file.drain_writes().await?; + + let after = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); + assert_eq!( + after.mtime, explicit_secs, + "explicit mtime must survive the deferred data commit" + ); + assert_eq!(after.mtime_nsec, explicit_nsec); + assert_eq!(after.atime, 11); + assert_eq!(after.atime_nsec, 13); + assert_eq!(after.size, 10); + assert_eq!(file.pread(0, 32).await?, b"stash body"); + + Ok(()) + } + + #[tokio::test] + async fn test_write_after_stashed_utimens_restamps_mtime_keeps_atime() -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let (stats, file) = fs + .create_file("/stash-then-write.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + + file.pwrite_ranges_batched(vec![WriteRange { + offset: 0, + data: b"first".to_vec(), + }]) + .await?; + + let stale_secs = 1_222_222_222; + FileSystem::utimens( + &fs, + stats.ino, + TimeChange::Set(33, 44), + TimeChange::Set(stale_secs, 0), + ) + .await?; + + // A write AFTER the stashed setattr means the file changed again: the + // commit must stamp fresh mtime/ctime. The explicitly-set atime is not + // affected by writes and must survive. + file.pwrite_ranges_batched(vec![WriteRange { + offset: 5, + data: b" second".to_vec(), + }]) + .await?; + + file.drain_writes().await?; + + let after = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); + assert!( + after.mtime > stale_secs, + "a write after the stashed setattr must bump mtime again (got {}, explicit was {})", + after.mtime, + stale_secs + ); + assert_eq!(after.atime, 33, "explicit atime must survive a later write"); + assert_eq!(after.atime_nsec, 44); + assert_eq!(file.pread(0, 32).await?, b"first second"); + + Ok(()) + } + + // Build a batcher with an explicit config so the test is independent of the + // process-global AGENTFS_BATCH_* env vars (which other tests mutate + // concurrently). Reuses `fs`'s pool/attr cache so commits hit real inodes. + fn test_batcher( + fs: &AgentFS, + batch_ms_secs: u64, + batch_bytes: usize, + batch_global_bytes: usize, + ) -> Arc { + let config = BatcherConfig { + window: std::time::Duration::from_secs(batch_ms_secs), + inode_bytes: batch_bytes, + global_bytes: batch_global_bytes, + ..BatcherConfig::default() + }; + Arc::new(AgentFSWriteBatcher::from_config( + fs.pool.clone(), + fs.chunk_size, + fs.inline_threshold, + { + let attr_cache = Arc::clone(&fs.attr_cache); + Arc::new(move |ino| attr_cache.remove(ino)) + }, + &config, + )) + } + + #[tokio::test] + async fn test_batcher_bytes_trigger_restamps_after_explicit_times() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (stats, _file) = fs + .create_file("/bytes-trigger-times.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + + // Long timer and huge global cap make the per-inode byte cap the only + // reachable synchronous drain trigger in this test. + let batcher = test_batcher(&fs, 600, 8, 1 << 30); + + batcher + .enqueue_for_test( + stats.ino, + vec![WriteRange { + offset: 0, + data: b"abcd".to_vec(), + }], + ) + .await?; + assert!( + batcher.has_pending(stats.ino), + "below the per-inode byte cap, the write must stay pending" + ); + + let explicit_secs = 1_345_678_901; + let explicit_nsec = 123; + let conn = fs.pool.get_connection().await?; + conn.execute( + "UPDATE fs_inode SET mtime = ?, mtime_nsec = ?, ctime = ?, ctime_nsec = ? WHERE ino = ?", + ( + explicit_secs, + explicit_nsec, + explicit_secs, + explicit_nsec, + stats.ino, + ), + ) + .await?; + batcher.mark_times_explicit(stats.ino); + + batcher + .enqueue_for_test( + stats.ino, + vec![WriteRange { + offset: 4, + data: b"efgh".to_vec(), + }], + ) + .await?; + assert!( + !batcher.has_pending(stats.ino), + "crossing the per-inode byte cap must drain this inode" + ); + + let after = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); + assert_eq!(after.size, 8); + assert!( + after.mtime > explicit_secs, + "the second write crosses the Bytes cap after the explicit setattr, \ + so the drain must stamp a fresh mtime (got {}, explicit was {})", + after.mtime, + explicit_secs + ); + assert_eq!( + fs.read_file("/bytes-trigger-times.txt").await?.unwrap(), + b"abcdefgh" + ); + Ok(()) + } + + #[tokio::test] + async fn test_batcher_global_cap_triggers_full_drain_and_tracks_total() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (sa, _fa) = fs.create_file("/a.bin", DEFAULT_FILE_MODE, 0, 0).await?; + let (sb, _fb) = fs.create_file("/b.bin", DEFAULT_FILE_MODE, 0, 0).await?; + + // 10-minute timer and huge per-inode trigger so the ONLY drain path is + // the 64-byte global cross-inode cap. + let batcher = test_batcher(&fs, 600, 1 << 20, 64); + + // Write below the cap to inode A: stays pending. + batcher + .enqueue_for_test( + sa.ino, + vec![WriteRange { + offset: 0, + data: vec![b'x'; 50], + }], + ) + .await?; + assert_eq!( + batcher.total_pending_bytes(), + 50, + "write below the global cap must remain in the overlay" + ); + + // Truncating into the pending range shrinks the tracked total. + batcher.truncate_pending(sa.ino, 20); + assert_eq!( + batcher.total_pending_bytes(), + 20, + "truncate_pending must shrink the running total to the kept prefix" + ); + + // Write to inode B crosses the cap (20 + 50 >= 64): a full batched drain + // commits every pending inode and resets the running total to zero. + batcher + .enqueue_for_test( + sb.ino, + vec![WriteRange { + offset: 0, + data: vec![b'y'; 50], + }], + ) + .await?; + assert_eq!( + batcher.total_pending_bytes(), + 0, + "crossing the global cap must drain all pending inodes" + ); + + // Committed data is intact and reflects the truncate. + assert_eq!(fs.read_file("/a.bin").await?.unwrap(), vec![b'x'; 20]); + assert_eq!(fs.read_file("/b.bin").await?.unwrap(), vec![b'y'; 50]); + Ok(()) + } + + #[tokio::test] + async fn test_batcher_discard_pending_updates_total() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (sa, _fa) = fs.create_file("/c.bin", DEFAULT_FILE_MODE, 0, 0).await?; + + // No timer/bytes/global drain: writes accumulate so we can observe the + // total before discarding. + let batcher = test_batcher(&fs, 600, 1 << 20, 1 << 30); + batcher + .enqueue_for_test( + sa.ino, + vec![WriteRange { + offset: 0, + data: vec![b'z'; 100], + }], + ) + .await?; + assert_eq!(batcher.total_pending_bytes(), 100); + + batcher.discard_pending(sa.ino); + assert_eq!( + batcher.total_pending_bytes(), + 0, + "discard_pending must subtract the discarded inode's bytes" + ); + Ok(()) + } + + #[tokio::test] + async fn file_truncate_to_zero() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let data: Vec = (0..100).collect(); + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + file.truncate(0).await?; + + assert!(fs.read_file("/test.txt").await?.unwrap().is_empty()); + assert_eq!(fs.stat("/test.txt").await?.unwrap().size, 0); + Ok(()) + } + + #[tokio::test] + async fn file_truncate_smaller_within_chunk() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let data: Vec = (0..100).collect(); + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + file.truncate(50).await?; + + let result = fs.read_file("/test.txt").await?.unwrap(); + assert_eq!(result.len(), 50); + assert_eq!(result, &data[..50]); + Ok(()) + } + + #[tokio::test] + async fn file_truncate_across_chunk_boundary() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let chunk_size = fs.chunk_size(); + let data: Vec = (0..(chunk_size * 3)).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + let new_size = chunk_size + chunk_size / 2; + file.truncate(new_size as u64).await?; + + let result = fs.read_file("/test.txt").await?.unwrap(); + assert_eq!(result.len(), new_size); + assert_eq!(result, &data[..new_size]); + Ok(()) + } + + #[tokio::test] + async fn file_truncate_extend_file() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let data: Vec = (0..50).collect(); + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + file.truncate(100).await?; + + let stats = fs.stat("/test.txt").await?.unwrap(); + assert_eq!(stats.size, 100); + let result = fs.read_file("/test.txt").await?.unwrap(); + assert_eq!(result.len(), 100); + assert_eq!(&result[..50], &data[..]); + Ok(()) + } + + #[tokio::test] + async fn file_truncate_nonexistent_open_fails() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let result = fs.open("/nonexistent.txt").await; + assert!(matches!(result, Err(Error::Fs(FsError::NotFound)))); + Ok(()) + } + + #[tokio::test] + async fn file_truncate_at_chunk_boundary() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let chunk_size = fs.chunk_size(); + let data: Vec = (0..(chunk_size * 3)).map(|i| (i % 256) as u8).collect(); + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &data).await?; + file.truncate(chunk_size as u64).await?; + + let result = fs.read_file("/test.txt").await?.unwrap(); + assert_eq!(result.len(), chunk_size); + assert_eq!(result, &data[..chunk_size]); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_file_same_directory() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let data = b"hello world"; + let (_, file) = fs.create_file("/old.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, data).await?; + rename_path_via_trait(&fs, "/old.txt", "/new.txt").await?; + + assert!(fs.stat("/old.txt").await?.is_none()); + assert_eq!(fs.read_file("/new.txt").await?.unwrap(), data); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_file_to_different_directory() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + fs.mkdir("/subdir", 0, 0).await?; + let data = b"test data"; + let (_, file) = fs.create_file("/file.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, data).await?; + rename_path_via_trait(&fs, "/file.txt", "/subdir/file.txt").await?; + + assert!(fs.stat("/file.txt").await?.is_none()); + assert_eq!(fs.read_file("/subdir/file.txt").await?.unwrap(), data); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_overwrite_existing_file() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs.create_file("/src.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, b"source").await?; + let (_, file) = fs.create_file("/dst.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, b"destination").await?; + rename_path_via_trait(&fs, "/src.txt", "/dst.txt").await?; + + assert!(fs.stat("/src.txt").await?.is_none()); + assert_eq!(fs.read_file("/dst.txt").await?.unwrap(), b"source"); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_directory() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + fs.mkdir("/olddir", 0, 0).await?; + let (_, file) = fs + .create_file("/olddir/file.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"content").await?; + rename_path_via_trait(&fs, "/olddir", "/newdir").await?; + + assert!(fs.stat("/olddir").await?.is_none()); + assert!(fs.stat("/newdir").await?.is_some()); + assert_eq!(fs.read_file("/newdir/file.txt").await?.unwrap(), b"content"); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_directory_into_own_subtree_fails() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + fs.mkdir("/parent", 0, 0).await?; + fs.mkdir("/parent/child", 0, 0).await?; + + let parent_ino = fs.stat("/parent").await?.unwrap().ino; + let child_ino = fs.stat("/parent/child").await?.unwrap().ino; + let root_before = fs.readdir(ROOT_INO).await?.unwrap(); + let parent_before = fs.readdir(parent_ino).await?.unwrap(); + let child_before = fs.readdir(child_ino).await?.unwrap(); + + let result = rename_path_via_trait(&fs, "/parent", "/parent/child/parent").await; + + assert!(matches!(result, Err(Error::Fs(FsError::InvalidRename)))); + assert_eq!(fs.readdir(ROOT_INO).await?.unwrap(), root_before); + assert_eq!(fs.readdir(parent_ino).await?.unwrap(), parent_before); + assert_eq!(fs.readdir(child_ino).await?.unwrap(), child_before); + assert!(fs.stat("/parent").await?.is_some()); + assert!(fs.stat("/parent/child").await?.is_some()); + assert!(fs.stat("/parent/child/parent").await?.is_none()); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_corrupt_dentry_cycle_returns_invalid_path() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + fs.mkdir("/src", 0, 0).await?; + fs.mkdir("/cycle-a", 0, 0).await?; + fs.mkdir("/cycle-a/cycle-b", 0, 0).await?; + + let cycle_a = fs.stat("/cycle-a").await?.unwrap().ino; + let cycle_b = fs.stat("/cycle-a/cycle-b").await?.unwrap().ino; + let conn = fs.pool.get_connection().await?; + conn.execute( + "UPDATE fs_dentry SET parent_ino = ? WHERE ino = ?", + (cycle_b, cycle_a), + ) + .await?; + fs.invalidate_dentry(ROOT_INO, "cycle-a"); + fs.invalidate_dentry(cycle_a, "cycle-b"); + + let result = tokio::time::timeout( + Duration::from_secs(2), + FileSystem::rename(&fs, ROOT_INO, "src", cycle_a, "moved"), + ) + .await + .expect("corrupt dentry cycles must error instead of looping forever"); + + assert!(matches!(result, Err(Error::Fs(FsError::InvalidPath)))); + assert!( + FileSystem::lookup(&fs, ROOT_INO, "src").await?.is_some(), + "failed guarded rename must preserve the source directory" + ); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_root_fails() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let result = rename_path_via_trait(&fs, "/", "/newroot").await; + assert!(matches!(result, Err(Error::Fs(FsError::RootOperation)))); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_to_root_fails() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs.create_file("/file.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, b"data").await?; + let result = rename_path_via_trait(&fs, "/file.txt", "/").await; + assert!(matches!(result, Err(Error::Fs(FsError::RootOperation)))); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_nonexistent_source_fails() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let result = rename_path_via_trait(&fs, "/nonexistent.txt", "/new.txt").await; + assert!(matches!(result, Err(Error::Fs(FsError::NotFound)))); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_overwrite_nonempty_directory_fails() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + fs.mkdir("/src", 0, 0).await?; + fs.mkdir("/dst", 0, 0).await?; + let (_, file) = fs + .create_file("/dst/file.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"content").await?; + + let result = rename_path_via_trait(&fs, "/src", "/dst").await; + assert!(matches!(result, Err(Error::Fs(FsError::NotEmpty)))); + assert!(fs.stat("/src").await?.is_some()); + assert!(fs.stat("/dst").await?.is_some()); + assert!(fs.stat("/dst/file.txt").await?.is_some()); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_file_to_directory_fails() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs.create_file("/file.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, b"data").await?; + fs.mkdir("/dir", 0, 0).await?; + + let result = rename_path_via_trait(&fs, "/file.txt", "/dir").await; + assert!(matches!(result, Err(Error::Fs(FsError::IsADirectory)))); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_directory_to_file_fails() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + fs.mkdir("/dir", 0, 0).await?; + let (_, file) = fs.create_file("/file.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, b"data").await?; + + let result = rename_path_via_trait(&fs, "/dir", "/file.txt").await; + assert!(matches!(result, Err(Error::Fs(FsError::NotADirectory)))); + Ok(()) + } + + #[tokio::test] + async fn trait_rename_updates_ctime() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs.create_file("/old.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, b"data").await?; + let stats_before = fs.stat("/old.txt").await?.unwrap(); + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + rename_path_via_trait(&fs, "/old.txt", "/new.txt").await?; + + let stats_after = fs.stat("/new.txt").await?.unwrap(); + assert!(stats_after.ctime >= stats_before.ctime); + Ok(()) + } + + #[tokio::test] + async fn test_chmod_regular_file() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Create a file with default permissions + let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, b"content").await?; + + let stats = fs.stat("/test.txt").await?.unwrap(); + let ino = stats.ino; + assert_eq!( + stats.mode & 0o7777, + 0o644, + "Default file mode should be 0o644" + ); + + // Change to executable + fs.chmod(ino, 0o755).await?; + + let stats = fs.stat("/test.txt").await?.unwrap(); + assert_eq!( + stats.mode & 0o7777, + 0o755, + "Mode should be 0o755 after chmod" + ); + assert!(stats.is_file(), "Should still be a regular file"); + + // Change to read-only + fs.chmod(ino, 0o444).await?; + + let stats = fs.stat("/test.txt").await?.unwrap(); + assert_eq!( + stats.mode & 0o7777, + 0o444, + "Mode should be 0o444 after chmod" + ); + + Ok(()) + } + + #[tokio::test] + async fn test_chmod_preserves_file_type() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Create a regular file + let (file_stats, file) = fs.create_file("/file.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, b"content").await?; + fs.chmod(file_stats.ino, 0o755).await?; + let stats = fs.stat("/file.txt").await?.unwrap(); + assert!(stats.is_file(), "Should remain a regular file after chmod"); + + // Create a directory + fs.mkdir("/dir", 0, 0).await?; + let dir_stats = fs.stat("/dir").await?.unwrap(); + fs.chmod(dir_stats.ino, 0o700).await?; + let stats = fs.stat("/dir").await?.unwrap(); + assert!( + stats.is_directory(), + "Should remain a directory after chmod" + ); + assert_eq!(stats.mode & 0o7777, 0o700, "Directory mode should be 0o700"); + + Ok(()) + } + + #[tokio::test] + async fn test_chmod_nonexistent_fails() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Use a non-existent inode + let result = fs.chmod(999999, 0o755).await; + assert!(result.is_err(), "chmod on nonexistent inode should fail"); + + Ok(()) + } + + #[tokio::test] + async fn test_chmod_symlink() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + + // Create target and symlink + let (_, file) = fs + .create_file("/target.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"content").await?; + FileSystem::symlink(&fs, ROOT_INO, "link.txt", "/target.txt", 0, 0).await?; + let link_stats = FileSystem::lookup(&fs, ROOT_INO, "link.txt") + .await? + .unwrap(); + + // chmod the symlink (should work on the symlink inode) + fs.chmod(link_stats.ino, 0o755).await?; + + let stats = FileSystem::lookup(&fs, ROOT_INO, "link.txt") + .await? + .unwrap(); + assert!(stats.is_symlink(), "Should still be a symlink"); + + Ok(()) + } + + // ==================== Tier Four: Overlay Read-After-Write ==================== + // + // These exercise the Tier 4 invariant that `pread` / `getattr` / + // `truncate` reflect pending batched writes BEFORE the SQLite drain + // commits them — i.e. the per-fd write-then-read story works without + // forcing a synchronous SQLite transaction on every read. + + #[tokio::test] + async fn pread_after_uncommitted_pwrite_sees_pending() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs + .create_file("/overlay.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"hello world").await?; + // No fsync — Tier 4 says the same fd must see its own writes via + // the in-memory overlay, regardless of whether SQLite has them yet. + assert_eq!(file.pread(0, 11).await?, b"hello world"); + assert_eq!(file.pread(6, 5).await?, b"world"); + Ok(()) + } + + #[tokio::test] + async fn pread_after_uncommitted_pwrite_partial_overlap() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs.create_file("/over.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, b"AAAAAAAAAA").await?; + file.fsync().await?; + file.pwrite(4, b"BBB").await?; + // Read spans SQLite-resident (A) and pending (B) regions. + assert_eq!(file.pread(2, 6).await?, b"AABBBA"); + Ok(()) + } + + #[tokio::test] + async fn pread_in_unwritten_region_returns_sqlite() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs.create_file("/hole.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(0, &[0xCDu8; 64]).await?; + file.fsync().await?; + file.pwrite(80, b"tail").await?; + // Read [16, 32) — entirely SQLite, no pending overlap. + assert_eq!(file.pread(16, 16).await?, vec![0xCDu8; 16]); + Ok(()) + } + + #[tokio::test] + async fn truncate_drops_pending_beyond_new_size() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs + .create_file("/trunc.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"abcdef").await?; + file.truncate(3).await?; + assert_eq!(file.pread(0, 16).await?, b"abc"); + let attrs = FileSystem::getattr(&fs, fs.resolve_path("/trunc.txt").await?.unwrap()) + .await? + .unwrap(); + assert_eq!(attrs.size, 3); + Ok(()) + } + + #[tokio::test] + async fn truncate_clips_range_spanning_boundary() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs.create_file("/clip.txt", DEFAULT_FILE_MODE, 0, 0).await?; + file.pwrite(2, b"PPPPPP").await?; + // pending occupies [2, 8). Truncate to 5 should keep [2, 5). + file.truncate(5).await?; + assert_eq!(file.pread(0, 16).await?, vec![0, 0, b'P', b'P', b'P']); + Ok(()) + } + + #[tokio::test] + async fn stat_coherent_before_drain() -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let (created, file) = fs.create_file("/grow.txt", DEFAULT_FILE_MODE, 0, 0).await?; + let pre = FileSystem::getattr(&fs, created.ino).await?.unwrap(); + assert_eq!(pre.size, 0); + file.pwrite(0, b"abcdefghij").await?; + assert!( + fs.write_batcher + .as_ref() + .is_some_and(|batcher| batcher.has_pending(created.ino)), + "long-window write should still be pending before stat" + ); + let conn = fs.pool.get_connection().await?; + let sqlite = store::getattr(&conn, created.ino) + .await? + .expect("file should exist"); + assert_eq!( + sqlite.size, 0, + "SQLite row should not be drained before the stat coherence check" + ); + drop(conn); + let post = FileSystem::getattr(&fs, created.ino).await?.unwrap(); + assert_eq!(post.size, 10); + Ok(()) + } + + #[tokio::test] + async fn versioned_cache_fill_skips_stale_attrs_after_drain() -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let (created, file) = fs + .create_file("/stale-cache.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + + file.pwrite(0, b"pending").await?; + + let conn = fs.pool.get_connection().await?; + let generation = fs.pending_generation(created.ino); + let mut stale_stats = store::getattr(&conn, created.ino) + .await? + .expect("file should exist"); + self::AgentFS::merge_pending_view(&fs, created.ino, Some(&mut stale_stats)); + drop(conn); + + file.drain_writes().await?; + fs.cache_attr_if_pending_generation(stale_stats, generation); + + assert!( + cached_attr(&fs, created.ino).is_none(), + "a cache fill captured before a racing drain must be skipped" + ); + + let current = FileSystem::getattr(&fs, created.ino) + .await? + .expect("file should still exist"); + assert_eq!(current.size, 7); + assert_eq!(file.pread(0, 16).await?, b"pending"); + Ok(()) + } + + #[tokio::test] + async fn retired_generations_prune_without_cache_fill_aba() -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let (created, file) = fs + .create_file("/pruned-generation.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + + file.pwrite(0, b"pending").await?; + + let conn = fs.pool.get_connection().await?; + let generation = fs.pending_generation(created.ino); + let mut stale_stats = store::getattr(&conn, created.ino) + .await? + .expect("file should exist"); + self::AgentFS::merge_pending_view(&fs, created.ino, Some(&mut stale_stats)); + drop(conn); + + file.drain_writes().await?; + let batcher = fs + .write_batcher + .as_ref() + .expect("long-window test config enables the batcher"); + assert!( + batcher.retired_generation_contains(created.ino), + "draining a pending entry should retire its generation before pruning" + ); + + for idx in 0..(batcher::MAX_RETIRED_GENERATIONS + 8) { + let path = format!("/retired-{idx}.txt"); + let (stats, _file) = fs.create_file(&path, DEFAULT_FILE_MODE, 0, 0).await?; + batcher + .enqueue_for_test( + stats.ino, + vec![WriteRange { + offset: 0, + data: vec![b'x'], + }], + ) + .await?; + batcher.discard_pending(stats.ino); + } + + assert!( + batcher.retired_generation_count() <= batcher::MAX_RETIRED_GENERATIONS, + "retired generations must stay bounded under inode churn" + ); + assert!( + !batcher.retired_generation_contains(created.ino), + "the oldest retired generation should have been pruned by the sweep" + ); + + fs.cache_attr_if_pending_generation(stale_stats, generation); + assert!( + cached_attr(&fs, created.ino).is_none(), + "a cache fill that observed the pre-drain generation must still be skipped after pruning" + ); + + let current = FileSystem::getattr(&fs, created.ino) + .await? + .expect("file should still exist"); + assert_eq!(current.size, 7); + assert_eq!(file.pread(0, 16).await?, b"pending"); + Ok(()) + } + + #[tokio::test] + async fn never_batched_attr_cache_fill_survives_unrelated_generation_bumps() -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let (stable, _stable_file) = fs + .create_file("/stable.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + fs.invalidate_attr(stable.ino); + + let conn = fs.pool.get_connection().await?; + let generation = fs.pending_generation(stable.ino); + let stable_stats = store::getattr(&conn, stable.ino) + .await? + .expect("stable file should exist"); + drop(conn); + + let (unrelated, unrelated_file) = fs + .create_file("/unrelated.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + unrelated_file.pwrite(0, b"unrelated").await?; + assert!( + fs.write_batcher + .as_ref() + .is_some_and(|batcher| batcher.has_pending(unrelated.ino)), + "unrelated write should bump the global generation without touching stable.txt" + ); + + fs.cache_attr_if_pending_generation(stable_stats, generation); + assert!( + cached_attr(&fs, stable.ino).is_some(), + "never-batched inode cache fill must not be suppressed by unrelated write traffic" + ); + + Ok(()) + } + + #[tokio::test] + async fn never_batched_attr_cache_fill_survives_prunes_at_retired_map_saturation( + ) -> Result<()> { + let (fs, _dir) = create_test_fs_with_config(test_config_with_long_batch_window()).await?; + let (stable, _stable_file) = fs + .create_file("/stable.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + fs.invalidate_attr(stable.ino); + let batcher = fs + .write_batcher + .as_ref() + .expect("long-window test config enables the batcher"); + + // Two synthetic retirements whose epoch order inverts their generation + // order (a long-lived pending entry retiring with its own older + // generation after a younger entry already retired with a newer one). + const HIGH_GENERATION_INO: i64 = 9001; + const LOW_GENERATION_INO: i64 = 9002; + batcher.retire_generation_for_test(HIGH_GENERATION_INO, u64::MAX); + batcher.retire_generation_for_test(LOW_GENERATION_INO, 1); + + // Saturate the retired map: the prune triggered by the last retirement + // removes the oldest entry (the high-generation one), lifting the + // watermark above every generation still tracked in the map. + for idx in 0..(batcher::MAX_RETIRED_GENERATIONS - 1) { + let path = format!("/saturation-{idx}.txt"); + let (stats, _file) = fs.create_file(&path, DEFAULT_FILE_MODE, 0, 0).await?; + batcher + .enqueue_for_test( + stats.ino, + vec![WriteRange { + offset: 0, + data: vec![b'x'], + }], + ) + .await?; + batcher.discard_pending(stats.ino); + } + assert!( + !batcher.retired_generation_contains(HIGH_GENERATION_INO), + "saturation should have pruned the oldest retired entry" + ); + assert!( + batcher.retired_generation_contains(LOW_GENERATION_INO), + "the low-generation entry must still be tracked (next prune victim)" + ); + assert_eq!( + batcher.retired_generation_count(), + batcher::MAX_RETIRED_GENERATIONS, + "the retired map must sit exactly at saturation" + ); + + // A never-batched cache fill observes the watermark and reads SQLite. + let conn = fs.pool.get_connection().await?; + let generation = fs.pending_generation(stable.ino); + let stable_stats = store::getattr(&conn, stable.ino) + .await? + .expect("stable file should exist"); + drop(conn); + + // Unrelated write traffic advances the global generation counter... + let (unrelated, unrelated_file) = fs + .create_file("/unrelated.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + unrelated_file.pwrite(0, b"unrelated").await?; + assert!(batcher.has_pending(unrelated.ino)); + + // ...and a concurrent prune lands inside the observe/compare window, + // removing only the low-generation entry already below the watermark. + let (churn, _churn_file) = fs + .create_file("/saturation-final.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + batcher + .enqueue_for_test( + churn.ino, + vec![WriteRange { + offset: 0, + data: vec![b'x'], + }], + ) + .await?; + batcher.discard_pending(churn.ino); + assert!( + !batcher.retired_generation_contains(LOW_GENERATION_INO), + "the concurrent retirement should have pruned the low-generation entry" + ); + + fs.cache_attr_if_pending_generation(stable_stats, generation); + assert!( + cached_attr(&fs, stable.ino).is_some(), + "at retired-map saturation a prune of already-covered generations \ + must not suppress never-batched inode cache fills" + ); + + Ok(()) + } + + #[tokio::test] + async fn concurrent_writers_overlay_merge() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, fh_a) = fs + .create_file("/multi.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + let ino = fs.resolve_path("/multi.txt").await?.unwrap(); + let fh_b = fs.open("/multi.txt").await?; + fh_a.pwrite(0, b"AAAA").await?; + fh_b.pwrite(4, b"BBBB").await?; + // Either fd should see both writes merged via the overlay. + assert_eq!(fh_a.pread(0, 8).await?, b"AAAABBBB"); + assert_eq!(fh_b.pread(0, 8).await?, b"AAAABBBB"); + // And getattr reflects the combined size. + let attrs = FileSystem::getattr(&fs, ino).await?.unwrap(); + assert_eq!(attrs.size, 8); + Ok(()) + } + + #[tokio::test] + async fn unlink_during_pending_writes_no_orphan() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (created, file) = fs + .create_file("/doomed.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"these bytes never reach SQLite").await?; + // Unlink before any drain. Tier 4 hooks discard_pending here. + fs.remove("/doomed.txt").await?; + // Force a batched drain. If pending was not discarded, the drain + // would hit NotFound while looking up fs_inode for the + // unlinked ino. The drain must therefore succeed. + fs.drain_all().await?; + // And the row truly is gone. + assert!(fs.stat("/doomed.txt").await?.is_none()); + let conn = fs.pool.get_connection().await?; + let count: i64 = { + let mut rows = conn + .query("SELECT COUNT(*) FROM fs_data WHERE ino = ?", (created.ino,)) + .await?; + rows.next() + .await? + .and_then(|r| r.get_value(0).ok().and_then(|v| v.as_integer().copied())) + .unwrap_or(-1) + }; + assert_eq!(count, 0, "no orphan fs_data rows for unlinked ino"); + Ok(()) + } + + #[tokio::test] + async fn fsync_drains_overlay_to_sqlite() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (created, file) = fs + .create_file("/durable.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + file.pwrite(0, b"persist me").await?; + // Before fsync, the bytes are in the overlay; get_chunk_count drains + // them as part of the test helper (Tier 4 sync helper change). + // After fsync, the chunk count should be observable without any + // helper drain prelude. + file.fsync().await?; + let conn = fs.pool.get_connection().await?; + let count: i64 = { + let mut rows = conn + .query("SELECT size FROM fs_inode WHERE ino = ?", (created.ino,)) + .await?; + rows.next() + .await? + .and_then(|r| r.get_value(0).ok().and_then(|v| v.as_integer().copied())) + .unwrap_or(-1) + }; + assert_eq!(count, 10, "fsync committed pending size to fs_inode"); + Ok(()) + } + + /// Spec acceptance criterion for Tier 4: + /// "`agentfs_batcher_drains_explicit / agentfs_batcher_enqueues` ratio + /// drops to <0.2 (vs ~1.0 today) — confirms read path no longer triggers + /// Explicit drains." + /// + /// We simulate a read-after-write workload (write, read, write, read, ...) + /// and assert that the SDK does NOT call drain_inode_writes + /// (Explicit drain) on every read. With Tier 4 the read path peeks the + /// overlay; with Tier 3 each read forces drain → ratio ≈ 1.0. + #[tokio::test] + async fn tier_four_drains_explicit_to_enqueues_ratio_under_0_2() -> Result<()> { + let (fs, _dir) = create_test_fs().await?; + let (_, file) = fs + .create_file("/ratio.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + + let pre = crate::telemetry::snapshot(); + let pre_enq = pre.counter("agentfs_batcher_enqueues"); + let pre_explicit = pre.counter("agentfs_batcher_drains_explicit"); + + // 200 write-then-read cycles, no intervening fsync. Tier 3 would + // drain Explicit on every read; Tier 4 must not. + for i in 0..200u64 { + file.pwrite(i * 4, b"abcd").await?; + let _ = file.pread(i * 4, 4).await?; + } + + let post = crate::telemetry::snapshot(); + let enq = post.counter("agentfs_batcher_enqueues") - pre_enq; + let explicit = post.counter("agentfs_batcher_drains_explicit") - pre_explicit; + assert!(enq >= 200, "expected ≥200 enqueues, got {enq}"); + let ratio = explicit as f64 / enq.max(1) as f64; + assert!( + ratio < 0.2, + "Tier 4 acceptance: drains_explicit/enqueues should be <0.2; \ + got {explicit}/{enq} = {ratio:.3}" + ); + Ok(()) + } + + /// Spec escape-hatch verification: with the overlay disabled, the SDK + /// reverts to Tier 3 drain-on-write semantics. `pwrite` should commit + /// straight to SQLite (no batcher enqueue), and `pread` should see the + /// value without ever consulting `peek_pending`. This locks in the kill + /// switch the spec's risk table called for. + #[tokio::test] + async fn overlay_reads_flag_off_falls_back_to_drain_on_write() -> Result<()> { + let (mut fs, _dir) = create_test_fs().await?; + fs.overlay_reads = false; + let (_, file) = fs + .create_file("/escape.bin", DEFAULT_FILE_MODE, 0, 0) + .await?; + + file.pwrite(0, b"hello world").await?; + // Per-inode check rather than the global enqueue counter: parallel + // tests share the profiling globals, so counter deltas race. + let escape_ino = fs.resolve_path("/escape.bin").await?.unwrap(); + if let Some(batcher) = &fs.write_batcher { + assert!( + !batcher.has_pending(escape_ino), + "with overlay_reads=false, pwrite must not enqueue" + ); + } + let got = file.pread(0, 11).await?; + assert_eq!(&got, b"hello world"); + + // And the file is durably in SQLite without an explicit fsync — + // the Tier 3 contract. + let ino = fs.resolve_path("/escape.bin").await?.unwrap(); + let conn = fs.pool.get_connection().await?; + let size: i64 = { + let mut rows = conn + .query("SELECT size FROM fs_inode WHERE ino = ?", (ino,)) + .await?; + rows.next() + .await? + .and_then(|r| r.get_value(0).ok().and_then(|v| v.as_integer().copied())) + .unwrap_or(-1) + }; + assert_eq!( + size, 11, + "overlay_reads=false → SQLite has full size after pwrite" + ); + Ok(()) + } diff --git a/crates/agentfs-core/src/fs/host/common.rs b/crates/agentfs-core/src/fs/host/common.rs new file mode 100644 index 00000000..1c45f70e --- /dev/null +++ b/crates/agentfs-core/src/fs/host/common.rs @@ -0,0 +1,136 @@ +use crate::error::{Error, Result}; +use crate::fs::{File, Stats}; +use async_trait::async_trait; +use std::os::unix::io::{AsRawFd, OwnedFd}; + +/// An open file handle for HostFS (real fd for I/O). +pub(super) struct HostFSFile { + /// Real file descriptor for read/write operations. + pub(super) fd: OwnedFd, +} + +#[async_trait] +impl File for HostFSFile { + async fn pread(&self, offset: u64, size: u64) -> Result> { + let fd = self.fd.as_raw_fd(); + tokio::task::spawn_blocking(move || { + let mut buf = vec![0u8; size as usize]; + let n = unsafe { + libc::pread( + fd, + buf.as_mut_ptr() as *mut libc::c_void, + size as usize, + offset as libc::off_t, + ) + }; + if n < 0 { + return Err(std::io::Error::last_os_error().into()); + } + buf.truncate(n as usize); + Ok(buf) + }) + .await + .map_err(|e| Error::Internal(e.to_string()))? + } + + async fn pwrite(&self, offset: u64, data: &[u8]) -> Result<()> { + let fd = self.fd.as_raw_fd(); + let data = data.to_vec(); + tokio::task::spawn_blocking(move || { + let n = unsafe { + libc::pwrite( + fd, + data.as_ptr() as *const libc::c_void, + data.len(), + offset as libc::off_t, + ) + }; + if n < 0 { + return Err(std::io::Error::last_os_error().into()); + } + Ok(()) + }) + .await + .map_err(|e| Error::Internal(e.to_string()))? + } + + async fn truncate(&self, size: u64) -> Result<()> { + let fd = self.fd.as_raw_fd(); + tokio::task::spawn_blocking(move || { + let result = unsafe { libc::ftruncate(fd, size as libc::off_t) }; + if result < 0 { + return Err(std::io::Error::last_os_error().into()); + } + Ok(()) + }) + .await + .map_err(|e| Error::Internal(e.to_string()))? + } + + async fn fsync(&self) -> Result<()> { + let fd = self.fd.as_raw_fd(); + tokio::task::spawn_blocking(move || { + let result = unsafe { libc::fsync(fd) }; + if result < 0 { + return Err(std::io::Error::last_os_error().into()); + } + Ok(()) + }) + .await + .map_err(|e| Error::Internal(e.to_string()))? + } + + async fn fstat(&self) -> Result { + let fd = self.fd.as_raw_fd(); + tokio::task::spawn_blocking(move || { + let mut stat: libc::stat = unsafe { std::mem::zeroed() }; + let result = unsafe { libc::fstat(fd, &mut stat) }; + if result < 0 { + return Err(std::io::Error::last_os_error().into()); + } + Ok(stat_to_stats(&stat)) + }) + .await + .map_err(|e| Error::Internal(e.to_string()))? + } +} + +/// Convert libc::stat to our Stats struct. +#[cfg(target_os = "linux")] +pub(super) fn stat_to_stats(stat: &libc::stat) -> Stats { + Stats { + ino: stat.st_ino as i64, + mode: stat.st_mode, + nlink: stat.st_nlink as u32, + uid: stat.st_uid, + gid: stat.st_gid, + size: stat.st_size, + atime: stat.st_atime, + mtime: stat.st_mtime, + ctime: stat.st_ctime, + atime_nsec: stat.st_atime_nsec as u32, + mtime_nsec: stat.st_mtime_nsec as u32, + ctime_nsec: stat.st_ctime_nsec as u32, + rdev: stat.st_rdev, + } +} + +/// Convert libc::stat to our Stats struct. +#[cfg(target_os = "macos")] +pub(super) fn stat_to_stats(stat: &libc::stat) -> Stats { + Stats { + ino: stat.st_ino as i64, + mode: stat.st_mode as u32, + nlink: stat.st_nlink as u32, + uid: stat.st_uid, + gid: stat.st_gid, + size: stat.st_size, + atime: stat.st_atime, + mtime: stat.st_mtime, + ctime: stat.st_ctime, + atime_nsec: stat.st_atime_nsec as u32, + mtime_nsec: stat.st_mtime_nsec as u32, + ctime_nsec: stat.st_ctime_nsec as u32, + rdev: stat.st_rdev as u64, + } +} diff --git a/sdk/rust/src/filesystem/hostfs_darwin.rs b/crates/agentfs-core/src/fs/host/darwin.rs similarity index 88% rename from sdk/rust/src/filesystem/hostfs_darwin.rs rename to crates/agentfs-core/src/fs/host/darwin.rs index e9180d34..af8fce02 100644 --- a/sdk/rust/src/filesystem/hostfs_darwin.rs +++ b/crates/agentfs-core/src/fs/host/darwin.rs @@ -4,18 +4,20 @@ //! O_PATH file descriptors. macOS doesn't support O_PATH or AT_EMPTY_PATH, //! so we use a path-based approach similar to libfuse's passthrough.c example. -use super::{BoxedFile, DirEntry, File, FileSystem, FilesystemStats, FsError, Stats, TimeChange}; use crate::error::{Error, Result}; +use crate::fs::{BoxedFile, DirEntry, FileSystem, FilesystemStats, FsError, Stats, TimeChange}; use async_trait::async_trait; use std::collections::HashMap; use std::ffi::{CStr, CString}; use std::os::unix::ffi::OsStrExt; -use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd}; -use std::path::PathBuf; +use std::os::unix::io::{FromRawFd, OwnedFd}; +use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use std::sync::RwLock; +use super::common::{stat_to_stats, HostFSFile}; + /// Root inode number (matches FUSE convention) pub const ROOT_INO: i64 = 1; @@ -57,117 +59,6 @@ pub struct HostFS { fuse_mountpoint_inode: Option, } -/// An open file handle for HostFS (real fd for I/O) -pub struct HostFSFile { - /// Real file descriptor for read/write operations - fd: OwnedFd, -} - -#[async_trait] -impl File for HostFSFile { - async fn pread(&self, offset: u64, size: u64) -> Result> { - let fd = self.fd.as_raw_fd(); - tokio::task::spawn_blocking(move || { - let mut buf = vec![0u8; size as usize]; - let n = unsafe { - libc::pread( - fd, - buf.as_mut_ptr() as *mut libc::c_void, - size as usize, - offset as libc::off_t, - ) - }; - if n < 0 { - return Err(std::io::Error::last_os_error().into()); - } - buf.truncate(n as usize); - Ok(buf) - }) - .await - .map_err(|e| Error::Internal(e.to_string()))? - } - - async fn pwrite(&self, offset: u64, data: &[u8]) -> Result<()> { - let fd = self.fd.as_raw_fd(); - let data = data.to_vec(); - tokio::task::spawn_blocking(move || { - let n = unsafe { - libc::pwrite( - fd, - data.as_ptr() as *const libc::c_void, - data.len(), - offset as libc::off_t, - ) - }; - if n < 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(()) - }) - .await - .map_err(|e| Error::Internal(e.to_string()))? - } - - async fn truncate(&self, size: u64) -> Result<()> { - let fd = self.fd.as_raw_fd(); - tokio::task::spawn_blocking(move || { - let result = unsafe { libc::ftruncate(fd, size as libc::off_t) }; - if result < 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(()) - }) - .await - .map_err(|e| Error::Internal(e.to_string()))? - } - - async fn fsync(&self) -> Result<()> { - let fd = self.fd.as_raw_fd(); - tokio::task::spawn_blocking(move || { - let result = unsafe { libc::fsync(fd) }; - if result < 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(()) - }) - .await - .map_err(|e| Error::Internal(e.to_string()))? - } - - async fn fstat(&self) -> Result { - let fd = self.fd.as_raw_fd(); - tokio::task::spawn_blocking(move || { - let mut stat: libc::stat = unsafe { std::mem::zeroed() }; - let result = unsafe { libc::fstat(fd, &mut stat) }; - if result < 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(stat_to_stats(&stat)) - }) - .await - .map_err(|e| Error::Internal(e.to_string()))? - } -} - -/// Convert libc::stat to our Stats struct -fn stat_to_stats(stat: &libc::stat) -> Stats { - Stats { - ino: stat.st_ino as i64, - mode: stat.st_mode as u32, - nlink: stat.st_nlink as u32, - uid: stat.st_uid, - gid: stat.st_gid, - size: stat.st_size, - atime: stat.st_atime, - mtime: stat.st_mtime, - ctime: stat.st_ctime, - atime_nsec: stat.st_atime_nsec as u32, - mtime_nsec: stat.st_mtime_nsec as u32, - ctime_nsec: stat.st_ctime_nsec as u32, - rdev: stat.st_rdev as u64, - } -} - /// Clear errno (platform-specific) #[inline] fn clear_errno() { @@ -252,7 +143,7 @@ impl HostFS { } /// Perform lstat on a path - fn lstat_path(path: &PathBuf) -> Result { + fn lstat_path(path: &Path) -> Result { let c_path = CString::new(path.as_os_str().as_bytes()) .map_err(|_| Error::Internal("invalid path".to_string()))?; let mut stat: libc::stat = unsafe { std::mem::zeroed() }; @@ -264,7 +155,7 @@ impl HostFS { } /// Open a file by path - fn open_path(path: &PathBuf, flags: libc::c_int) -> Result { + fn open_path(path: &Path, flags: libc::c_int) -> Result { let c_path = CString::new(path.as_os_str().as_bytes()) .map_err(|_| Error::Internal("invalid path".to_string()))?; let fd = unsafe { libc::open(c_path.as_ptr(), flags) }; @@ -545,7 +436,11 @@ impl FileSystem for HostFS { let mut stats = stat_to_stats(&stat); stats.ino = child_ino; - result.push(DirEntry { name, stats }); + result.push(DirEntry { + name, + cookie: stats.ino, + stats, + }); } result.sort_by(|a, b| a.name.cmp(&b.name)); diff --git a/sdk/rust/src/filesystem/hostfs_linux.rs b/crates/agentfs-core/src/fs/host/linux.rs similarity index 89% rename from sdk/rust/src/filesystem/hostfs_linux.rs rename to crates/agentfs-core/src/fs/host/linux.rs index 14e66f86..0b0b2213 100644 --- a/sdk/rust/src/filesystem/hostfs_linux.rs +++ b/crates/agentfs-core/src/fs/host/linux.rs @@ -1,5 +1,5 @@ -use super::{BoxedFile, DirEntry, File, FileSystem, FilesystemStats, FsError, Stats, TimeChange}; use crate::error::{Error, Result}; +use crate::fs::{BoxedFile, DirEntry, FileSystem, FilesystemStats, FsError, Stats, TimeChange}; use async_trait::async_trait; use std::collections::HashMap; use std::ffi::{CStr, CString}; @@ -10,6 +10,8 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use std::sync::RwLock; +use super::common::{stat_to_stats, HostFSFile}; + /// Root inode number (matches FUSE convention) pub const ROOT_INO: i64 = 1; @@ -54,117 +56,6 @@ pub struct HostFS { fuse_mountpoint_inode: Option, } -/// An open file handle for HostFS (real fd for I/O) -pub struct HostFSFile { - /// Real file descriptor for read/write operations - fd: OwnedFd, -} - -#[async_trait] -impl File for HostFSFile { - async fn pread(&self, offset: u64, size: u64) -> Result> { - let fd = self.fd.as_raw_fd(); - tokio::task::spawn_blocking(move || { - let mut buf = vec![0u8; size as usize]; - let n = unsafe { - libc::pread( - fd, - buf.as_mut_ptr() as *mut libc::c_void, - size as usize, - offset as libc::off_t, - ) - }; - if n < 0 { - return Err(std::io::Error::last_os_error().into()); - } - buf.truncate(n as usize); - Ok(buf) - }) - .await - .map_err(|e| Error::Internal(e.to_string()))? - } - - async fn pwrite(&self, offset: u64, data: &[u8]) -> Result<()> { - let fd = self.fd.as_raw_fd(); - let data = data.to_vec(); - tokio::task::spawn_blocking(move || { - let n = unsafe { - libc::pwrite( - fd, - data.as_ptr() as *const libc::c_void, - data.len(), - offset as libc::off_t, - ) - }; - if n < 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(()) - }) - .await - .map_err(|e| Error::Internal(e.to_string()))? - } - - async fn truncate(&self, size: u64) -> Result<()> { - let fd = self.fd.as_raw_fd(); - tokio::task::spawn_blocking(move || { - let result = unsafe { libc::ftruncate(fd, size as libc::off_t) }; - if result < 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(()) - }) - .await - .map_err(|e| Error::Internal(e.to_string()))? - } - - async fn fsync(&self) -> Result<()> { - let fd = self.fd.as_raw_fd(); - tokio::task::spawn_blocking(move || { - let result = unsafe { libc::fsync(fd) }; - if result < 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(()) - }) - .await - .map_err(|e| Error::Internal(e.to_string()))? - } - - async fn fstat(&self) -> Result { - let fd = self.fd.as_raw_fd(); - tokio::task::spawn_blocking(move || { - let mut stat: libc::stat = unsafe { std::mem::zeroed() }; - let result = unsafe { libc::fstat(fd, &mut stat) }; - if result < 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(stat_to_stats(&stat)) - }) - .await - .map_err(|e| Error::Internal(e.to_string()))? - } -} - -/// Convert libc::stat to our Stats struct -fn stat_to_stats(stat: &libc::stat) -> Stats { - Stats { - ino: stat.st_ino as i64, - mode: stat.st_mode, - nlink: stat.st_nlink as u32, - uid: stat.st_uid, - gid: stat.st_gid, - size: stat.st_size, - atime: stat.st_atime, - mtime: stat.st_mtime, - ctime: stat.st_ctime, - atime_nsec: stat.st_atime_nsec as u32, - mtime_nsec: stat.st_mtime_nsec as u32, - ctime_nsec: stat.st_ctime_nsec as u32, - rdev: stat.st_rdev, - } -} - impl HostFS { /// Create a new HostFS rooted at the given directory pub fn new(root: impl Into) -> Result { @@ -576,7 +467,11 @@ impl FileSystem for HostFS { let mut stats = stat_to_stats(&stat); stats.ino = child_ino; - result.push(DirEntry { name, stats }); + result.push(DirEntry { + name, + cookie: stats.ino, + stats, + }); } result.sort_by(|a, b| a.name.cmp(&b.name)); diff --git a/crates/agentfs-core/src/fs/host/mod.rs b/crates/agentfs-core/src/fs/host/mod.rs new file mode 100644 index 00000000..79887abd --- /dev/null +++ b/crates/agentfs-core/src/fs/host/mod.rs @@ -0,0 +1,11 @@ +#[cfg(any(target_os = "macos", target_os = "linux"))] +mod common; +#[cfg(target_os = "macos")] +mod darwin; +#[cfg(target_os = "linux")] +mod linux; + +#[cfg(target_os = "macos")] +pub use darwin::HostFS; +#[cfg(target_os = "linux")] +pub use linux::HostFS; diff --git a/sdk/rust/src/filesystem/mod.rs b/crates/agentfs-core/src/fs/mod.rs similarity index 73% rename from sdk/rust/src/filesystem/mod.rs rename to crates/agentfs-core/src/fs/mod.rs index fe044c70..9a1bf1cf 100644 --- a/sdk/rust/src/filesystem/mod.rs +++ b/crates/agentfs-core/src/fs/mod.rs @@ -1,9 +1,6 @@ pub mod agentfs; -#[cfg(target_os = "macos")] -pub mod hostfs_darwin; -#[cfg(target_os = "linux")] -pub mod hostfs_linux; -pub mod overlayfs; +pub mod host; +pub mod overlay; use crate::error::Result; use async_trait::async_trait; @@ -11,14 +8,12 @@ use std::sync::Arc; use thiserror::Error; // Re-export implementations -pub use agentfs::{ - keepcache_delta_enabled, AgentFS, ImportEntry, ImportOptions, ImportSession, ImportedEntry, -}; +pub use agentfs::{AgentFS, ImportEntry, ImportOptions, ImportSession, ImportedEntry}; #[cfg(target_os = "macos")] -pub use hostfs_darwin::HostFS; +pub use host::HostFS; #[cfg(target_os = "linux")] -pub use hostfs_linux::HostFS; -pub use overlayfs::{ +pub use host::HostFS; +pub use overlay::{ OverlayFS, PartialOriginMode, PartialOriginPolicy, DEFAULT_PARTIAL_ORIGIN_THRESHOLD_BYTES, }; @@ -49,14 +44,29 @@ pub enum FsError { #[error("Cannot modify root directory")] RootOperation, + #[error("Permission denied")] + PermissionDenied, + + #[error("Operation not permitted")] + OperationNotPermitted, + #[error("Too many levels of symbolic links")] SymlinkLoop, #[error("Cannot rename directory into its own subdirectory")] InvalidRename, + #[error("Cross-device link")] + CrossDevice, + #[error("Filename too long")] NameTooLong, + + #[error("Bad directory cookie")] + BadCookie, + + #[error("Filesystem metadata is corrupt: {0}")] + Corrupt(String), } impl FsError { @@ -71,9 +81,14 @@ impl FsError { FsError::NotASymlink => libc::EINVAL, FsError::InvalidPath => libc::EINVAL, FsError::RootOperation => libc::EPERM, + FsError::PermissionDenied => libc::EACCES, + FsError::OperationNotPermitted => libc::EPERM, FsError::SymlinkLoop => libc::ELOOP, FsError::InvalidRename => libc::EINVAL, + FsError::CrossDevice => libc::EXDEV, FsError::NameTooLong => libc::ENAMETOOLONG, + FsError::BadCookie => libc::EINVAL, + FsError::Corrupt(_) => libc::EIO, } } } @@ -140,6 +155,35 @@ pub struct DirEntry { pub name: String, /// Full statistics for this entry pub stats: Stats, + /// Opaque directory cookie for resuming after this entry. + pub cookie: i64, +} + +/// A cookie-addressed directory page. +#[derive(Debug, Clone)] +pub struct DirEntryPage { + /// Entries after the requested cookie, up to the requested page limit. + pub entries: Vec, + /// Whether this page reached the end of the directory. + pub end: bool, +} + +/// Kernel-cache coherence class for an inode. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum KernelCachePolicy { + /// Inode contents can only change through this filesystem implementation, + /// so adapter invalidations bound kernel-cache staleness. + Stable, + /// Inode contents may change outside this filesystem implementation. + /// Adapters must avoid long-lived kernel page, attr, and entry cache + /// grants that could present stale bytes as current. + ExternalDrift, +} + +impl KernelCachePolicy { + pub fn has_external_drift(self) -> bool { + matches!(self, Self::ExternalDrift) + } } /// A byte range to write at a fixed file offset. @@ -257,6 +301,40 @@ pub trait FileSystem: Send + Sync { /// Returns `Ok(None)` if the directory does not exist. async fn readdir_plus(&self, ino: i64) -> Result>>; + /// List a bounded page of directory entries after `start_after`. + /// + /// `start_after` is the opaque cookie returned with the previous entry. A + /// zero cookie starts at the beginning. Implementations with an ordered + /// directory index should override this method so callers do not need to + /// enumerate all prior entries for every page. A positive cookie that is + /// no longer present is [`FsError::BadCookie`]. + async fn readdir_plus_after( + &self, + ino: i64, + start_after: i64, + max_entries: usize, + ) -> Result> { + let Some(entries) = self.readdir_plus(ino).await? else { + return Ok(None); + }; + + let start = if start_after > 0 { + entries + .iter() + .position(|entry| entry.cookie == start_after) + .map(|index| index + 1) + .ok_or(FsError::BadCookie)? + } else { + 0 + }; + let end = start.saturating_add(max_entries).min(entries.len()); + + Ok(Some(DirEntryPage { + entries: entries[start..end].to_vec(), + end: end >= entries.len(), + })) + } + /// Change file mode/permissions by inode. async fn chmod(&self, ino: i64, mode: u32) -> Result<()>; @@ -285,6 +363,18 @@ pub trait FileSystem: Send + Sync { Ok(None) } + /// Whether a FUSE adapter may use an adapter-local attr-cache fast path for + /// DB-backed keep-cache decisions before falling back to + /// [`FileSystem::keep_cache_for_read_open`]. + fn delta_keep_cache_fast_path(&self) -> bool { + false + } + + /// Return the kernel-cache coherence class for an inode. + fn kernel_cache_policy(&self, _ino: i64) -> KernelCachePolicy { + KernelCachePolicy::Stable + } + /// Create a directory with the specified ownership. /// /// Returns the stats of the newly created directory. @@ -355,6 +445,29 @@ pub trait FileSystem: Send + Sync { newname: &str, ) -> Result<()>; + /// Rename/move a file or directory and return the inode replaced at the + /// destination, if any. + /// + /// Implementations with transactional namespace state should override this + /// so the destination inode is resolved in the same operation that performs + /// the replacement. The default preserves legacy semantics for simple + /// passthrough filesystems. + async fn rename_with_replaced_ino( + &self, + oldparent_ino: i64, + oldname: &str, + newparent_ino: i64, + newname: &str, + ) -> Result> { + let replaced_ino = self + .lookup(newparent_ino, newname) + .await? + .map(|stats| stats.ino); + self.rename(oldparent_ino, oldname, newparent_ino, newname) + .await?; + Ok(replaced_ino) + } + /// Get filesystem statistics. async fn statfs(&self) -> Result; @@ -373,6 +486,15 @@ pub trait FileSystem: Send + Sync { self.drain_all().await } + /// Register a hook that runs when this filesystem reaps an inode. + /// + /// Filesystems without an AgentFS lifecycle return `false`; callers should + /// still invalidate adapter-local state around explicit unlink/rename + /// operations when they need immediate semantics over wrapper inode spaces. + fn register_reap_hook(&self, _hook: Arc) -> bool { + false + } + /// Retain an existing lookup reference without resolving a name again. /// /// FUSE positive LOOKUP cache hits still create kernel lookup references. diff --git a/crates/agentfs-core/src/fs/overlay/copyup.rs b/crates/agentfs-core/src/fs/overlay/copyup.rs new file mode 100644 index 00000000..d3941d51 --- /dev/null +++ b/crates/agentfs-core/src/fs/overlay/copyup.rs @@ -0,0 +1,506 @@ +use super::*; +use crate::fs::{BoxedFile, FsError}; + +impl OverlayFS { + pub(super) async fn resolve_base_path(&self, path: &str) -> Result> { + let mut ino = ROOT_INO; + if path == "/" { + return self.base.getattr(ino).await; + } + + let mut stats = None; + for component in path.split('/').filter(|s| !s.is_empty()) { + let Some(next) = self.base.lookup(ino, component).await? else { + return Ok(None); + }; + ino = next.ino; + stats = Some(next); + } + Ok(stats) + } + + pub(super) async fn resolves_to_visible_base_directory(&self, path: &str) -> Result { + if self.is_whiteout(path) { + return Ok(false); + } + + Ok(self + .resolve_base_path(path) + .await? + .is_some_and(|stats| stats.is_directory())) + } + + pub(super) fn validate_partial_origin( + &self, + origin: &PartialOrigin, + stats: &Stats, + ) -> Result<()> { + if stats.size != origin.base_fingerprint_size { + return Err(Error::Internal(format!( + "partial-origin base changed for {} (stored size={}, current size={})", + origin.base_path, origin.base_fingerprint_size, stats.size + ))); + } + if stats.mtime != origin.base_mtime + || stats.mtime_nsec != origin.base_mtime_nsec + || stats.ctime != origin.base_ctime + || stats.ctime_nsec != origin.base_ctime_nsec + { + return Err(Error::Internal(format!( + "partial-origin base changed for {} (stored mtime={}.{}, current mtime={}.{}, stored ctime={}.{}, current ctime={}.{})", + origin.base_path, + origin.base_mtime, + origin.base_mtime_nsec, + stats.mtime, + stats.mtime_nsec, + origin.base_ctime, + origin.base_ctime_nsec, + stats.ctime, + stats.ctime_nsec + ))); + } + Ok(()) + } + + pub(super) async fn cleanup_partial_origin_if_unlinked(&self, delta_ino: i64) -> Result<()> { + let conn = self.delta.get_connection().await?; + let mut rows = conn + .query("SELECT 1 FROM fs_inode WHERE ino = ?", (delta_ino,)) + .await?; + if rows.next().await?.is_some() { + return Ok(()); + } + + conn.execute("DELETE FROM fs_origin WHERE delta_ino = ?", (delta_ino,)) + .await?; + conn.execute( + "DELETE FROM fs_chunk_override WHERE delta_ino = ?", + (delta_ino,), + ) + .await?; + conn.execute( + "DELETE FROM fs_partial_origin WHERE delta_ino = ?", + (delta_ino,), + ) + .await?; + Ok(()) + } + + /// Promote an overlay inode from base layer to delta layer. + /// + /// When a directory that was originally looked up from base gets a + /// corresponding directory created in delta (via ensure_parent_dirs), + /// we need to update the overlay inode to point to delta. This ensures + /// that operations like readdir and unlink will check the delta layer. + pub(super) fn promote_to_delta(&self, path: &str, delta_ino: i64) { + self.promote_mapping_to_delta(path, delta_ino); + } + + /// Resolve the delta-layer inode for a parent directory. + /// + /// If the parent's overlay inode already maps to Delta, returns the underlying + /// inode directly. Otherwise, walks the delta filesystem from root using the + /// stored path. Returns Ok(None) if any path component is missing in delta. + pub(super) async fn resolve_delta_parent(&self, info: &InodeInfo) -> Result> { + if info.layer == Layer::Delta { + return Ok(Some(info.underlying_ino)); + } + let mut ino: i64 = 1; + for comp in info.path.split('/').filter(|s| !s.is_empty()) { + match FileSystem::lookup(&self.delta, ino, comp).await? { + Some(s) if s.is_directory() => ino = s.ino, + Some(_) => return Ok(None), + None => return Ok(None), + } + } + Ok(Some(ino)) + } + + /// Ensure parent directories exist in delta layer + pub(super) async fn ensure_parent_dirs(&self, path: &str, uid: u32, gid: u32) -> Result<()> { + let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); + + let mut current_path = String::new(); + let mut current_delta_ino: i64 = 1; // Delta root + let mut current_base_ino: i64 = 1; // Base root + + for component in components.iter().take(components.len().saturating_sub(1)) { + current_path = format!("{}/{}", current_path, component); + + // Remove any whiteout for this path + self.remove_whiteout(¤t_path).await?; + + // Check if directory exists in delta + if let Some(stats) = + FileSystem::lookup(&self.delta, current_delta_ino, component).await? + { + if stats.is_directory() { + current_delta_ino = stats.ino; + // Advance base in parallel so it stays in sync + if let Some(bs) = self.base.lookup(current_base_ino, component).await? { + current_base_ino = bs.ino; + } + continue; + } else { + return Err(FsError::NotADirectory.into()); + } + } + + // Not in delta, check base (using the base inode, not delta inode) + let base_stats = self.base.lookup(current_base_ino, component).await?; + let (dir_uid, dir_gid, origin_base_ino) = if let Some(s) = &base_stats { + let base_ino = s.ino; + current_base_ino = base_ino; + (s.uid, s.gid, Some(base_ino)) + } else { + (uid, gid, None) + }; + + // Create directory in delta + let new_stats = FileSystem::mkdir( + &self.delta, + current_delta_ino, + component, + 0o755, + dir_uid, + dir_gid, + ) + .await?; + current_delta_ino = new_stats.ino; + + // Create origin mapping if directory exists in base, so that + // lookups return consistent overlay inodes + if let Some(base_ino) = origin_base_ino { + self.add_origin_mapping(new_stats.ino, base_ino).await?; + // Promote the overlay inode to delta so readdir/unlink will check delta + self.promote_to_delta(¤t_path, new_stats.ino); + } + } + + Ok(()) + } + + /// Copy a file from base to delta for modification + pub(super) async fn copy_up(&self, path: &str, base_ino: i64) -> Result { + // Parse path to get parent and name + let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); + if components.is_empty() { + return Err(FsError::RootOperation.into()); + } + let name = components.last().unwrap(); + + // Check if already copied up - walk delta to find parent and check for file + let mut parent_ino: i64 = 1; + let mut found_parent = true; + for comp in components.iter().take(components.len() - 1) { + if let Some(stats) = FileSystem::lookup(&self.delta, parent_ino, comp).await? { + parent_ino = stats.ino; + } else { + found_parent = false; + break; + } + } + + // If parent exists in delta, check if file already exists there + if found_parent { + if let Some(stats) = FileSystem::lookup(&self.delta, parent_ino, name).await? { + // Already copied up, return delta inode + return Ok(stats.ino); + } + } + + // Get base stats + let base_stats = self + .base + .getattr(base_ino) + .await? + .ok_or(FsError::NotFound)?; + + // Ensure parent directories exist + self.ensure_parent_dirs(path, base_stats.uid, base_stats.gid) + .await?; + + // Look up parent in delta by walking the path + let mut parent_ino: i64 = 1; // Start at delta root + for comp in components.iter().take(components.len() - 1) { + let stats = FileSystem::lookup(&self.delta, parent_ino, comp) + .await? + .ok_or(FsError::NotFound)?; + parent_ino = stats.ino; + } + + // Copy based on file type + let delta_ino = if base_stats.is_symlink() { + let target = self + .base + .readlink(base_ino) + .await? + .ok_or(FsError::NotFound)?; + let stats = FileSystem::symlink( + &self.delta, + parent_ino, + name, + &target, + base_stats.uid, + base_stats.gid, + ) + .await?; + stats.ino + } else if base_stats.is_directory() { + let stats = FileSystem::mkdir( + &self.delta, + parent_ino, + name, + base_stats.mode & 0o7777, + base_stats.uid, + base_stats.gid, + ) + .await?; + stats.ino + } else { + // Regular file - read content and create + let base_file = self.base.open(base_ino, libc::O_RDONLY).await?; + let content = base_file.pread(0, base_stats.size as u64).await?; + + let (stats, delta_file) = FileSystem::create_file( + &self.delta, + parent_ino, + name, + base_stats.mode, + base_stats.uid, + base_stats.gid, + ) + .await?; + delta_file.pwrite(0, &content).await?; + stats.ino + }; + + // Store origin mapping + self.add_origin_mapping(delta_ino, base_ino).await?; + + Ok(delta_ino) + } + + /// Copy-up a file and update the inode mapping so subsequent operations + /// go to the delta layer. Returns the delta inode. + pub(super) async fn copy_up_and_update_mapping( + &self, + overlay_ino: i64, + info: &InodeInfo, + ) -> Result { + let delta_ino = self.copy_up(&info.path, info.underlying_ino).await?; + self.refresh_overlay_mapping(overlay_ino, Layer::Delta, delta_ino, &info.path); + Ok(delta_ino) + } + + pub(super) async fn partial_copy_up_and_update_mapping( + &self, + overlay_ino: i64, + info: &InodeInfo, + ) -> Result { + let components: Vec<&str> = info.path.split('/').filter(|s| !s.is_empty()).collect(); + if components.is_empty() { + return Err(FsError::RootOperation.into()); + } + let name = components.last().unwrap(); + + let base_stats = match self.resolve_base_path(&info.path).await? { + Some(stats) => stats, + None => self + .base + .getattr(info.underlying_ino) + .await? + .ok_or(FsError::NotFound)?, + }; + if !base_stats.is_file() { + return self.copy_up_and_update_mapping(overlay_ino, info).await; + } + + self.ensure_parent_dirs(&info.path, base_stats.uid, base_stats.gid) + .await?; + + let mut parent_ino = ROOT_INO; + for comp in components.iter().take(components.len() - 1) { + let stats = FileSystem::lookup(&self.delta, parent_ino, comp) + .await? + .ok_or(FsError::NotFound)?; + parent_ino = stats.ino; + } + + if let Some(stats) = FileSystem::lookup(&self.delta, parent_ino, name).await? { + self.refresh_overlay_mapping(overlay_ino, Layer::Delta, stats.ino, &info.path); + return Ok(stats.ino); + } + + let (stats, _file) = FileSystem::create_file( + &self.delta, + parent_ino, + name, + base_stats.mode, + base_stats.uid, + base_stats.gid, + ) + .await?; + let delta_ino = stats.ino; + + let conn = self.delta.get_connection().await?; + conn.execute( + "UPDATE fs_inode + SET mode = ?, uid = ?, gid = ?, size = ?, atime = ?, mtime = ?, ctime = ?, + atime_nsec = ?, mtime_nsec = ?, ctime_nsec = ?, data_inline = NULL, storage_kind = ? + WHERE ino = ?", + ( + base_stats.mode as i64, + base_stats.uid as i64, + base_stats.gid as i64, + base_stats.size, + base_stats.atime, + base_stats.mtime, + base_stats.ctime, + base_stats.atime_nsec as i64, + base_stats.mtime_nsec as i64, + base_stats.ctime_nsec as i64, + STORAGE_CHUNKED, + delta_ino, + ), + ) + .await?; + self.delta.invalidate_attr(delta_ino); + + self.add_origin_mapping(delta_ino, info.underlying_ino) + .await?; + self.add_partial_origin_mapping(delta_ino, info.underlying_ino, &info.path, &base_stats) + .await?; + self.refresh_overlay_mapping(overlay_ino, Layer::Delta, delta_ino, &info.path); + + Ok(delta_ino) + } + + pub(super) async fn partial_file_for_delta( + &self, + overlay_ino: i64, + delta_ino: i64, + flags: i32, + ) -> Result { + if let Some(origin) = self.partial_origin_for_delta(delta_ino).await? { + let base_stats = self + .resolve_base_path(&origin.base_path) + .await? + .ok_or(FsError::NotFound)?; + self.validate_partial_origin(&origin, &base_stats)?; + let base_file = self.base.open(base_stats.ino, libc::O_RDONLY).await?; + + // Tier Two Axis C: HostFS passthrough for unmodified delta files. + // + // A partial-origin delta inode that has zero chunk overrides, zero + // full chunks, no inline override, and a size matching the base is + // byte-identical to the base file. In that case the + // OverlayPartialFile wrapper would do a chunk-merge that always + // hits the "no override; read from base" branch -- the SQLite + // round trip is pure overhead. Returning the HostFS fd directly + // sends pread() straight to the kernel VFS for every read on this + // handle, which is most of the cost on `git status` / `git diff` + // / agent stat-storms over a working tree that was copy-up'd but + // not modified. + // + // Restricted to read-only opens: a write open MUST go through the + // OverlayPartialFile wrapper so writes land as `fs_chunk_override` + // rows in the delta DB and never touch the real base file + // (no-real-write invariant from Tier One). + if !is_write_open(flags) { + crate::telemetry::record_base_fast_open_passthrough_attempted(); + if self + .delta_has_no_content_overrides(delta_ino, base_stats.size) + .await? + { + crate::telemetry::record_base_fast_open_passthrough_succeeded(); + return Ok(mount_visible_file(base_file, overlay_ino)); + } + crate::telemetry::record_base_fast_open_passthrough_fallback(); + } + + let file: BoxedFile = Arc::new(OverlayPartialFile { + delta: self.delta.clone(), + base: self.base.clone(), + base_file, + origin, + overlay_ino, + delta_ino, + chunk_size: self.delta.chunk_size(), + }); + if (flags & libc::O_TRUNC) != 0 { + file.truncate(0).await?; + } + Ok(file) + } else { + FileSystem::open(&self.delta, delta_ino, flags).await + } + } + + /// Returns true if the delta inode has no content modifications: no chunk + /// overrides, no full chunks, no inline override, and size matches the + /// base. Such a delta is purely a metadata copy and reads can bypass the + /// `OverlayPartialFile` merge path entirely. + /// + /// This is the cheap "is this file unmodified?" check that Tier Two Axis + /// C uses to decide whether `partial_file_for_delta` can short-circuit to + /// a HostFS fd. + pub(super) async fn delta_has_no_content_overrides( + &self, + delta_ino: i64, + base_size: i64, + ) -> Result { + let conn = self.delta.get_connection().await?; + + // Any per-chunk override? + let mut rows = conn + .query( + "SELECT 1 FROM fs_chunk_override WHERE delta_ino = ? LIMIT 1", + (delta_ino,), + ) + .await?; + if rows.next().await?.is_some() { + return Ok(false); + } + + // Any full chunk in fs_data? (Should be implied by no overrides for + // partial-origin files, but check defensively in case of a + // partial-origin → fully-overridden transition.) + let mut rows = conn + .query("SELECT 1 FROM fs_data WHERE ino = ? LIMIT 1", (delta_ino,)) + .await?; + if rows.next().await?.is_some() { + return Ok(false); + } + + // Size match + no inline override? + let mut rows = conn + .query( + "SELECT size, data_inline FROM fs_inode WHERE ino = ?", + (delta_ino,), + ) + .await?; + let Some(row) = rows.next().await? else { + return Ok(false); + }; + let delta_size: i64 = row + .get(0) + .map_err(|e| Error::Internal(format!("fs_inode.size read failed: {e}")))?; + if delta_size != base_size { + return Ok(false); + } + let inline_value = row + .get_value(1) + .map_err(|e| Error::Internal(format!("fs_inode.data_inline read failed: {e}")))?; + let inline_empty = match inline_value { + Value::Null => true, + Value::Blob(blob) => blob.is_empty(), + _ => true, + }; + if !inline_empty { + return Ok(false); + } + + Ok(true) + } +} diff --git a/crates/agentfs-core/src/fs/overlay/fs.rs b/crates/agentfs-core/src/fs/overlay/fs.rs new file mode 100644 index 00000000..b2171b43 --- /dev/null +++ b/crates/agentfs-core/src/fs/overlay/fs.rs @@ -0,0 +1,907 @@ +use super::*; +use crate::fs::{ + BoxedFile, DirEntry, FileSystem, FilesystemStats, FsError, KernelCachePolicy, Stats, TimeChange, +}; +use async_trait::async_trait; +use std::collections::{HashMap, HashSet}; +use tracing::trace; + +#[async_trait] +impl FileSystem for OverlayFS { + async fn lookup(&self, parent_ino: i64, name: &str) -> Result> { + crate::telemetry::record_lookup(); + trace!( + "OverlayFS::lookup: parent_ino={}, name={}", + parent_ino, + name + ); + + let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; + let path = self.build_path(parent_ino, name)?; + + // Check for whiteout + if self.is_whiteout(&path) { + crate::telemetry::record_lookup_whiteout(); + crate::telemetry::record_negative_lookup(); + return Ok(None); + } + + // Try delta first + let delta_parent_ino = self.resolve_delta_parent(&parent_info).await?; + + // Look up in delta (only if we resolved the correct parent) + if let Some(delta_stats) = match delta_parent_ino { + Some(ino) => { + crate::telemetry::record_lookup_delta(); + self.delta.lookup(ino, name).await? + } + None => None, + } { + let delta_ino = delta_stats.ino; + let ino = self.get_or_create_overlay_ino(Layer::Delta, delta_ino, &path); + let mut stats = delta_stats; + + // Origin mapping: reuse an existing Base overlay inode for stable + // numbering within a session. After remount the base_ino stored in + // the mapping may be stale (the new HostFS has a fresh inode cache), + // so only use it when the reverse_map already contains a live entry. + // Otherwise keep the Delta overlay inode — the downstream code + // already walks base from root when the parent is tagged Delta. + if let Some(base_ino) = self.get_origin_ino(stats.ino) { + if let Some(existing_ino) = self.live_origin_overlay_ino(base_ino, &path) { + self.refresh_overlay_mapping(existing_ino, Layer::Delta, delta_ino, &path); + stats.ino = existing_ino; + } else { + stats.ino = ino; + } + } else { + stats.ino = ino; + } + + return Ok(Some(stats)); + } + + // Try base + let base_parent_ino = if parent_info.layer == Layer::Base { + parent_info.underlying_ino + } else { + // Need to find corresponding base parent by path + // For root, use base root (1) + if parent_info.path == "/" { + 1 + } else { + // Walk the base to find the parent + let mut base_ino: i64 = 1; + let components: Vec<_> = parent_info + .path + .split('/') + .filter(|s| !s.is_empty()) + .collect(); + crate::telemetry::record_path_resolution(components.len() as u64); + for comp in components { + if let Some(s) = self.base.lookup(base_ino, comp).await? { + base_ino = s.ino; + } else { + crate::telemetry::record_negative_lookup(); + return Ok(None); + } + } + base_ino + } + }; + + crate::telemetry::record_lookup_base(); + if let Some(base_stats) = self.base.lookup(base_parent_ino, name).await? { + let ino = self.get_or_create_overlay_ino(Layer::Base, base_stats.ino, &path); + let mut stats = base_stats; + stats.ino = ino; + return Ok(Some(stats)); + } + + crate::telemetry::record_negative_lookup(); + Ok(None) + } + + async fn getattr(&self, ino: i64) -> Result> { + crate::telemetry::record_getattr(); + crate::telemetry::record_attr_cache_miss(); + trace!("OverlayFS::getattr: ino={}", ino); + + let info = match self.get_inode_info(ino) { + Some(i) => i, + None => return Ok(None), + }; + if info.layer == Layer::Base && self.is_whiteout(&info.path) { + crate::telemetry::record_lookup_whiteout(); + return Ok(None); + } + + match info.layer { + Layer::Delta => Ok(FileSystem::getattr(&self.delta, info.underlying_ino) + .await? + .map(|mut s| { + s.ino = ino; + s + })), + Layer::Base => { + let stats = self.resolve_base_path(&info.path).await?; + Ok(stats.map(|mut s| { + self.refresh_overlay_mapping(ino, Layer::Base, s.ino, &info.path); + s.ino = ino; + s + })) + } + } + } + + async fn readlink(&self, ino: i64) -> Result> { + trace!("OverlayFS::readlink: ino={}", ino); + + let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; + if info.layer == Layer::Base && self.is_whiteout(&info.path) { + return Ok(None); + } + + match info.layer { + Layer::Delta => FileSystem::readlink(&self.delta, info.underlying_ino).await, + Layer::Base => self.base.readlink(info.underlying_ino).await, + } + } + + async fn readdir(&self, ino: i64) -> Result>> { + crate::telemetry::record_readdir(); + trace!("OverlayFS::readdir: ino={}", ino); + + let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; + let child_whiteouts = self.get_child_whiteouts(&info.path); + + let mut entries = HashSet::new(); + + // Get delta entries + if info.layer == Layer::Delta { + if let Some(delta_entries) = self.delta.readdir(info.underlying_ino).await? { + for entry in delta_entries { + let entry_path = if info.path == "/" { + format!("/{}", entry) + } else { + format!("{}/{}", info.path, entry) + }; + if !self.is_whiteout(&entry_path) && !child_whiteouts.contains(&entry) { + entries.insert(entry); + } + } + } + } + + // Get base entries (need to resolve base inode from path) + let base_ino = if info.layer == Layer::Base { + Some(info.underlying_ino) + } else { + // Walk base to find corresponding directory + let components: Vec<&str> = info.path.split('/').filter(|s| !s.is_empty()).collect(); + let mut ino: i64 = 1; + let mut found_all = true; + crate::telemetry::record_path_resolution(components.len() as u64); + for comp in &components { + if let Some(s) = self.base.lookup(ino, comp).await? { + ino = s.ino; + } else { + found_all = false; + break; + } + } + if found_all { + Some(ino) + } else { + None + } + }; + + if let Some(base_ino) = base_ino { + if let Some(base_entries) = self.base.readdir(base_ino).await? { + for entry in base_entries { + let entry_path = if info.path == "/" { + format!("/{}", entry) + } else { + format!("{}/{}", info.path, entry) + }; + if !self.is_whiteout(&entry_path) && !child_whiteouts.contains(&entry) { + entries.insert(entry); + } + } + } + } + + let mut result: Vec<_> = entries.into_iter().collect(); + result.sort(); + Ok(Some(result)) + } + + async fn readdir_plus(&self, ino: i64) -> Result>> { + crate::telemetry::record_readdir_plus(); + trace!("OverlayFS::readdir_plus: ino={}", ino); + + let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; + let child_whiteouts = self.get_child_whiteouts(&info.path); + + let mut entries_map: HashMap = HashMap::new(); + + // Get base entries first (so delta can override) + let base_ino = if info.layer == Layer::Base { + Some(info.underlying_ino) + } else { + let components: Vec<&str> = info.path.split('/').filter(|s| !s.is_empty()).collect(); + let mut ino: i64 = 1; + let mut found_all = true; + crate::telemetry::record_path_resolution(components.len() as u64); + for comp in &components { + if let Some(s) = self.base.lookup(ino, comp).await? { + ino = s.ino; + } else { + found_all = false; + break; + } + } + if found_all { + Some(ino) + } else { + None + } + }; + + if let Some(base_ino) = base_ino { + if let Some(base_entries) = self.base.readdir_plus(base_ino).await? { + for mut entry in base_entries { + let entry_path = if info.path == "/" { + format!("/{}", entry.name) + } else { + format!("{}/{}", info.path, entry.name) + }; + + if !self.is_whiteout(&entry_path) && !child_whiteouts.contains(&entry.name) { + let overlay_ino = self.get_or_create_overlay_ino( + Layer::Base, + entry.stats.ino, + &entry_path, + ); + entry.stats.ino = overlay_ino; + entries_map.insert(entry.name.clone(), entry); + } + } + } + } + + // Get delta entries (override base) + if info.layer == Layer::Delta { + if let Some(delta_entries) = self.delta.readdir_plus(info.underlying_ino).await? { + for mut entry in delta_entries { + let entry_path = if info.path == "/" { + format!("/{}", entry.name) + } else { + format!("{}/{}", info.path, entry.name) + }; + if self.is_whiteout(&entry_path) || child_whiteouts.contains(&entry.name) { + continue; + } + + // Check for origin mapping + let delta_ino = entry.stats.ino; + if let Some(base_ino) = self.get_origin_ino(entry.stats.ino) { + let overlay_ino = + self.get_or_create_overlay_ino(Layer::Delta, delta_ino, &entry_path); + if let Some(existing_ino) = + self.live_origin_overlay_ino(base_ino, &entry_path) + { + self.refresh_overlay_mapping( + existing_ino, + Layer::Delta, + delta_ino, + &entry_path, + ); + entry.stats.ino = existing_ino; + } else { + entry.stats.ino = overlay_ino; + } + } else { + let overlay_ino = self.get_or_create_overlay_ino( + Layer::Delta, + entry.stats.ino, + &entry_path, + ); + entry.stats.ino = overlay_ino; + } + + entries_map.insert(entry.name.clone(), entry); + } + } + } + + let mut result: Vec<_> = entries_map.into_values().collect(); + result.sort_by(|a, b| a.name.cmp(&b.name)); + for (index, entry) in result.iter_mut().enumerate() { + entry.cookie = (index + 1) as i64; + } + Ok(Some(result)) + } + + async fn chmod(&self, ino: i64, mode: u32) -> Result<()> { + trace!("OverlayFS::chmod: ino={}, mode={:o}", ino, mode); + + let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; + if info.layer == Layer::Base && self.is_whiteout(&info.path) { + return Err(FsError::NotFound.into()); + } + + let delta_ino = match info.layer { + Layer::Delta => info.underlying_ino, + Layer::Base => { + let base_stats = self + .base + .getattr(info.underlying_ino) + .await? + .ok_or(FsError::NotFound)?; + if self.partial_origin_policy.permits(&base_stats) { + self.partial_copy_up_and_update_mapping(ino, &info).await? + } else { + self.copy_up_and_update_mapping(ino, &info).await? + } + } + }; + + self.delta.chmod(delta_ino, mode).await + } + + async fn chown(&self, ino: i64, uid: Option, gid: Option) -> Result<()> { + trace!( + "OverlayFS::chown: ino={}, uid={:?}, gid={:?}", + ino, + uid, + gid + ); + + let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; + if info.layer == Layer::Base && self.is_whiteout(&info.path) { + return Err(FsError::NotFound.into()); + } + + let delta_ino = match info.layer { + Layer::Delta => info.underlying_ino, + Layer::Base => { + let base_stats = self + .base + .getattr(info.underlying_ino) + .await? + .ok_or(FsError::NotFound)?; + if self.partial_origin_policy.permits(&base_stats) { + self.partial_copy_up_and_update_mapping(ino, &info).await? + } else { + self.copy_up_and_update_mapping(ino, &info).await? + } + } + }; + + self.delta.chown(delta_ino, uid, gid).await + } + + async fn utimens(&self, ino: i64, atime: TimeChange, mtime: TimeChange) -> Result<()> { + trace!("OverlayFS::utimens: ino={}", ino); + + let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; + if info.layer == Layer::Base && self.is_whiteout(&info.path) { + return Err(FsError::NotFound.into()); + } + + let delta_ino = match info.layer { + Layer::Delta => info.underlying_ino, + Layer::Base => { + let base_stats = self + .base + .getattr(info.underlying_ino) + .await? + .ok_or(FsError::NotFound)?; + if self.partial_origin_policy.permits(&base_stats) { + self.partial_copy_up_and_update_mapping(ino, &info).await? + } else { + self.copy_up_and_update_mapping(ino, &info).await? + } + } + }; + + self.delta.utimens(delta_ino, atime, mtime).await + } + + async fn keep_cache_for_read_open(&self, ino: i64, flags: i32) -> Result> { + if is_write_open(flags) { + return Ok(None); + } + + let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; + match info.layer { + Layer::Base => Ok(None), + // Delta (DB-backed) files inherit the AgentFS keep-cache policy: + // the adapter fingerprint guard revalidates per open. + Layer::Delta => { + FileSystem::keep_cache_for_read_open(&self.delta, info.underlying_ino, flags).await + } + } + } + + fn delta_keep_cache_fast_path(&self) -> bool { + self.delta.delta_keep_cache_fast_path() + } + + fn kernel_cache_policy(&self, ino: i64) -> KernelCachePolicy { + match self.get_inode_info(ino) { + Some(info) if info.layer == Layer::Base && !self.is_whiteout(&info.path) => { + KernelCachePolicy::ExternalDrift + } + _ => KernelCachePolicy::Stable, + } + } + + async fn open(&self, ino: i64, flags: i32) -> Result { + trace!("OverlayFS::open: ino={}", ino); + + let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; + if info.layer == Layer::Base && self.is_whiteout(&info.path) { + return Err(FsError::NotFound.into()); + } + + match info.layer { + Layer::Delta => { + return self + .partial_file_for_delta(ino, info.underlying_ino, flags) + .await; + } + Layer::Base if !is_write_open(flags) => { + let current = self + .resolve_base_path(&info.path) + .await? + .ok_or(FsError::NotFound)?; + self.refresh_overlay_mapping(ino, Layer::Base, current.ino, &info.path); + let base_file = self.base.open(current.ino, flags).await?; + return Ok(mount_visible_file(base_file, ino)); + } + Layer::Base => { + let base_stats = self + .resolve_base_path(&info.path) + .await? + .ok_or(FsError::NotFound)?; + self.refresh_overlay_mapping(ino, Layer::Base, base_stats.ino, &info.path); + if self.partial_origin_policy.permits(&base_stats) { + let delta_ino = self.partial_copy_up_and_update_mapping(ino, &info).await?; + return self.partial_file_for_delta(ino, delta_ino, flags).await; + } + } + } + + let delta_ino = self.copy_up_and_update_mapping(ino, &info).await?; + + FileSystem::open(&self.delta, delta_ino, flags).await + } + + async fn mkdir( + &self, + parent_ino: i64, + name: &str, + mode: u32, + uid: u32, + gid: u32, + ) -> Result { + trace!("OverlayFS::mkdir: parent_ino={}, name={}", parent_ino, name); + + let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; + let path = self.build_path(parent_ino, name)?; + + // Check if already exists + if self.lookup(parent_ino, name).await?.is_some() { + return Err(FsError::AlreadyExists.into()); + } + + // Remove whiteout if exists + self.remove_whiteout(&path).await?; + + // Ensure parent dirs exist in delta + self.ensure_parent_dirs(&path, uid, gid).await?; + + let delta_parent_ino = self + .resolve_delta_parent(&parent_info) + .await? + .ok_or(FsError::NotFound)?; + + let mut stats = + FileSystem::mkdir(&self.delta, delta_parent_ino, name, mode, uid, gid).await?; + let overlay_ino = self.get_or_create_overlay_ino(Layer::Delta, stats.ino, &path); + stats.ino = overlay_ino; + + Ok(stats) + } + + async fn create_file( + &self, + parent_ino: i64, + name: &str, + mode: u32, + uid: u32, + gid: u32, + ) -> Result<(Stats, BoxedFile)> { + trace!( + "OverlayFS::create_file: parent_ino={}, name={}", + parent_ino, + name + ); + + let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; + let path = self.build_path(parent_ino, name)?; + + // Remove whiteout if exists + self.remove_whiteout(&path).await?; + + // Ensure parent dirs exist in delta + self.ensure_parent_dirs(&path, uid, gid).await?; + + let delta_parent_ino = self + .resolve_delta_parent(&parent_info) + .await? + .ok_or(FsError::NotFound)?; + + let (mut stats, file) = + FileSystem::create_file(&self.delta, delta_parent_ino, name, mode, uid, gid).await?; + let overlay_ino = self.get_or_create_overlay_ino(Layer::Delta, stats.ino, &path); + stats.ino = overlay_ino; + + Ok((stats, file)) + } + + async fn mknod( + &self, + parent_ino: i64, + name: &str, + mode: u32, + rdev: u64, + uid: u32, + gid: u32, + ) -> Result { + trace!("OverlayFS::mknod: parent_ino={}, name={}", parent_ino, name); + + let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; + let path = self.build_path(parent_ino, name)?; + + self.remove_whiteout(&path).await?; + self.ensure_parent_dirs(&path, uid, gid).await?; + + let delta_parent_ino = self + .resolve_delta_parent(&parent_info) + .await? + .ok_or(FsError::NotFound)?; + + let mut stats = + FileSystem::mknod(&self.delta, delta_parent_ino, name, mode, rdev, uid, gid).await?; + let overlay_ino = self.get_or_create_overlay_ino(Layer::Delta, stats.ino, &path); + stats.ino = overlay_ino; + + Ok(stats) + } + + async fn symlink( + &self, + parent_ino: i64, + name: &str, + target: &str, + uid: u32, + gid: u32, + ) -> Result { + trace!( + "OverlayFS::symlink: parent_ino={}, name={}, target={}", + parent_ino, + name, + target + ); + + let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; + let path = self.build_path(parent_ino, name)?; + + self.remove_whiteout(&path).await?; + self.ensure_parent_dirs(&path, uid, gid).await?; + + let delta_parent_ino = self + .resolve_delta_parent(&parent_info) + .await? + .ok_or(FsError::NotFound)?; + + let mut stats = + FileSystem::symlink(&self.delta, delta_parent_ino, name, target, uid, gid).await?; + let overlay_ino = self.get_or_create_overlay_ino(Layer::Delta, stats.ino, &path); + stats.ino = overlay_ino; + + Ok(stats) + } + + async fn unlink(&self, parent_ino: i64, name: &str) -> Result<()> { + trace!( + "OverlayFS::unlink: parent_ino={}, name={}", + parent_ino, + name + ); + + let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; + let path = self.build_path(parent_ino, name)?; + + // Check if it exists + let stats = self + .lookup(parent_ino, name) + .await? + .ok_or(FsError::NotFound)?; + if stats.is_directory() { + return Err(FsError::IsADirectory.into()); + } + + // Try to remove from delta. Walk the delta layer to find the parent, + // since the overlay parent may map to Base even when a copy-up exists in delta. + if let Some(dpi) = self.resolve_delta_parent(&parent_info).await? { + let removed_delta_ino = FileSystem::lookup(&self.delta, dpi, name) + .await? + .map(|stats| stats.ino); + match FileSystem::unlink(&self.delta, dpi, name).await { + Ok(()) => {} + Err(crate::error::Error::Fs(FsError::NotFound)) => {} + Err(e) => return Err(e), + } + if let Some(delta_ino) = removed_delta_ino { + self.cleanup_partial_origin_if_unlinked(delta_ino).await?; + } + } + + // If the file is still visible through the overlay after delta removal, + // it must be coming from the base layer — create a whiteout to hide it. + if self.lookup(parent_ino, name).await?.is_some() { + self.create_whiteout(&path).await?; + } + + Ok(()) + } + + async fn rmdir(&self, parent_ino: i64, name: &str) -> Result<()> { + trace!("OverlayFS::rmdir: parent_ino={}, name={}", parent_ino, name); + + let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; + let path = self.build_path(parent_ino, name)?; + + // Check if it exists and is a directory + let stats = self + .lookup(parent_ino, name) + .await? + .ok_or(FsError::NotFound)?; + if !stats.is_directory() { + return Err(FsError::NotADirectory.into()); + } + + // Check if directory is empty (in overlay view) + let dir_entries = self.readdir(stats.ino).await?.unwrap_or_default(); + if !dir_entries.is_empty() { + return Err(FsError::NotEmpty.into()); + } + + // Try to remove from delta. Walk the delta layer to find the parent, + // since the overlay parent may map to Base even when a copy-up exists in delta. + if let Some(dpi) = self.resolve_delta_parent(&parent_info).await? { + match FileSystem::rmdir(&self.delta, dpi, name).await { + Ok(()) => {} + Err(crate::error::Error::Fs(FsError::NotFound)) => {} + Err(e) => return Err(e), + } + } + + // If the directory is still visible through the overlay after delta removal, + // it must be coming from the base layer — create a whiteout to hide it. + if self.lookup(parent_ino, name).await?.is_some() { + self.create_whiteout(&path).await?; + } + + Ok(()) + } + + async fn link(&self, ino: i64, newparent_ino: i64, newname: &str) -> Result { + trace!( + "OverlayFS::link: ino={}, newparent_ino={}, newname={}", + ino, + newparent_ino, + newname + ); + + let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; + let parent_info = self + .get_inode_info(newparent_ino) + .ok_or(FsError::NotFound)?; + let new_path = self.build_path(newparent_ino, newname)?; + + // Ensure file is in delta (copy up if needed) + let delta_ino = if info.layer == Layer::Delta { + info.underlying_ino + } else { + self.copy_up(&info.path, info.underlying_ino).await? + }; + + self.remove_whiteout(&new_path).await?; + self.ensure_parent_dirs(&new_path, 0, 0).await?; + + // Resolve delta parent AFTER ensure_parent_dirs so the directories exist. + let delta_parent_ino = self + .resolve_delta_parent(&parent_info) + .await? + .ok_or(FsError::NotFound)?; + + let mut stats = FileSystem::link(&self.delta, delta_ino, delta_parent_ino, newname).await?; + stats.ino = ino; // Keep original overlay inode + + Ok(stats) + } + + async fn rename( + &self, + oldparent_ino: i64, + oldname: &str, + newparent_ino: i64, + newname: &str, + ) -> Result<()> { + self.rename_with_replaced_ino(oldparent_ino, oldname, newparent_ino, newname) + .await + .map(|_| ()) + } + + async fn rename_with_replaced_ino( + &self, + oldparent_ino: i64, + oldname: &str, + newparent_ino: i64, + newname: &str, + ) -> Result> { + trace!( + "OverlayFS::rename: oldparent={}, oldname={}, newparent={}, newname={}", + oldparent_ino, + oldname, + newparent_ino, + newname + ); + + let old_parent_info = self + .get_inode_info(oldparent_ino) + .ok_or(FsError::NotFound)?; + let new_parent_info = self + .get_inode_info(newparent_ino) + .ok_or(FsError::NotFound)?; + let old_path = self.build_path(oldparent_ino, oldname)?; + let new_path = self.build_path(newparent_ino, newname)?; + + // Get source stats + let src_stats = self + .lookup(oldparent_ino, oldname) + .await? + .ok_or(FsError::NotFound)?; + let src_info = self + .get_inode_info(src_stats.ino) + .ok_or(FsError::NotFound)?; + + // A base-origin directory copy-up only creates the directory itself, + // not its subtree. ensure_parent_dirs can promote such a directory to + // Layer::Delta after a single child write, so the layer tag alone is not + // a safe origin test. If the old path still resolves to a visible base + // directory, return EXDEV so user-space callers such as `mv` perform + // copy+delete. + if src_stats.is_directory() && self.resolves_to_visible_base_directory(&old_path).await? { + return Err(FsError::CrossDevice.into()); + } + + let replaced_ino = self + .lookup(newparent_ino, newname) + .await? + .map(|stats| stats.ino); + + // Remove any destination whiteout before source copy-up. The whiteout + // mutation runs in its own IMMEDIATE transaction and can fail under + // fault injection; doing it first prevents a failed rename from leaving + // source copy-up/origin state behind. + self.remove_whiteout(&new_path).await?; + + // Ensure source is in delta after destination whiteout removal succeeds. + let delta_src_ino = if src_info.layer == Layer::Base { + self.copy_up(&old_path, src_info.underlying_ino).await? + } else { + src_info.underlying_ino + }; + + self.ensure_parent_dirs(&new_path, 0, 0).await?; + + // Resolve delta parents AFTER copy_up / ensure_parent_dirs, + // since those create the parent directories in delta. + let delta_src_parent_ino = self + .resolve_delta_parent(&old_parent_info) + .await? + .ok_or(FsError::NotFound)?; + let delta_dst_parent_ino = self + .resolve_delta_parent(&new_parent_info) + .await? + .ok_or(FsError::NotFound)?; + + // Perform rename in delta + FileSystem::rename( + &self.delta, + delta_src_parent_ino, + oldname, + delta_dst_parent_ino, + newname, + ) + .await?; + self.refresh_overlay_mapping(src_stats.ino, Layer::Delta, delta_src_ino, &new_path); + + // If the old file is still visible through the overlay after the rename, + // it must be coming from the base layer — create a whiteout to hide it. + if self.lookup(oldparent_ino, oldname).await?.is_some() { + self.create_whiteout(&old_path).await?; + } + + Ok(replaced_ino) + } + + async fn statfs(&self) -> Result { + FileSystem::statfs(&self.delta).await + } + + async fn drain_inode_writes(&self, ino: i64) -> Result<()> { + let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; + match info.layer { + Layer::Delta => FileSystem::drain_inode_writes(&self.delta, info.underlying_ino).await, + Layer::Base => self.base.drain_inode_writes(info.underlying_ino).await, + } + } + + async fn drain_all(&self) -> Result<()> { + FileSystem::drain_all(&self.delta).await?; + self.base.drain_all().await?; + Ok(()) + } + + async fn finalize(&self) -> Result<()> { + FileSystem::finalize(&self.delta).await?; + self.base.finalize().await?; + Ok(()) + } + + async fn retain_lookup(&self, ino: i64, nlookup: u64) -> Result<()> { + let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; + match info.layer { + Layer::Delta => { + FileSystem::retain_lookup(&self.delta, info.underlying_ino, nlookup).await? + } + Layer::Base => { + self.base + .retain_lookup(info.underlying_ino, nlookup) + .await? + } + } + self.retain_overlay_lookup(ino, nlookup); + Ok(()) + } + + async fn forget(&self, ino: i64, nlookup: u64) { + // Look up the inode info to determine which layer it belongs to before + // pruning the overlay maps. + let info = match self.get_inode_info(ino) { + Some(i) => i, + None => return, // Unknown inode, nothing to forget + }; + + match info.layer { + Layer::Delta => { + // Delta (AgentFS) doesn't cache fds, but call it anyway for completeness. + FileSystem::forget(&self.delta, info.underlying_ino, nlookup).await; + } + Layer::Base => { + // Base layer (HostFS) caches O_PATH fds and needs forget. + self.base.forget(info.underlying_ino, nlookup).await; + } + } + + self.forget_overlay_lookup(ino, nlookup); + } +} diff --git a/crates/agentfs-core/src/fs/overlay/maps.rs b/crates/agentfs-core/src/fs/overlay/maps.rs new file mode 100644 index 00000000..27f26a47 --- /dev/null +++ b/crates/agentfs-core/src/fs/overlay/maps.rs @@ -0,0 +1,279 @@ +use super::{OverlayFS, ROOT_INO}; +use crate::error::Result; +use crate::fs::FsError; +use std::collections::HashMap; + +/// Which layer an inode belongs to. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(super) enum Layer { + Delta, + Base, +} + +/// Information about an inode in the overlay filesystem. +#[derive(Debug, Clone)] +pub(super) struct InodeInfo { + /// Which layer this inode lives in. + pub(super) layer: Layer, + /// The inode number in the underlying layer. + pub(super) underlying_ino: i64, + /// Virtual path (for whiteout and copy-up operations). + pub(super) path: String, + /// Whether this inode also has an origin reverse-map entry to remove. + has_extra_reverse: bool, +} + +#[cfg(test)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) struct OverlayMapCounts { + pub(super) inode_entries: usize, + pub(super) reverse_entries: usize, + pub(super) path_entries: usize, + pub(super) lookup_ref_entries: usize, +} + +/// All overlay inode maps protected by a single mutex. +pub(super) struct OverlayMaps { + inode: HashMap, + reverse: HashMap<(Layer, i64), i64>, + path: HashMap, + lookup_refs: HashMap, + next_ino: i64, +} + +impl OverlayMaps { + pub(super) fn new() -> Self { + let mut inode = HashMap::new(); + let mut reverse = HashMap::new(); + let mut path = HashMap::new(); + + inode.insert( + ROOT_INO, + InodeInfo { + layer: Layer::Delta, + underlying_ino: ROOT_INO, + path: "/".to_string(), + has_extra_reverse: false, + }, + ); + reverse.insert((Layer::Delta, ROOT_INO), ROOT_INO); + path.insert("/".to_string(), ROOT_INO); + + Self { + inode, + reverse, + path, + lookup_refs: HashMap::new(), + next_ino: ROOT_INO + 1, + } + } + + fn alloc_ino(&mut self) -> i64 { + let ino = self.next_ino; + self.next_ino += 1; + ino + } + + fn get_or_create(&mut self, layer: Layer, underlying_ino: i64, path: &str) -> i64 { + if let Some(&ino) = self.reverse.get(&(layer, underlying_ino)) { + self.retain_lookup(ino, 1); + return ino; + } + + let ino = self.alloc_ino(); + self.inode.insert( + ino, + InodeInfo { + layer, + underlying_ino, + path: path.to_string(), + has_extra_reverse: false, + }, + ); + self.reverse.insert((layer, underlying_ino), ino); + self.path.insert(path.to_string(), ino); + self.lookup_refs.insert(ino, 1); + ino + } + + fn refresh( + &mut self, + overlay_ino: i64, + new_layer: Layer, + new_underlying_ino: i64, + new_path: &str, + ) { + let Some(info) = self.inode.get_mut(&overlay_ino) else { + return; + }; + let old_layer = info.layer; + let old_underlying_ino = info.underlying_ino; + let old_path = info.path.clone(); + let keeps_origin_reverse = info.layer == Layer::Base && new_layer == Layer::Delta; + info.layer = new_layer; + info.underlying_ino = new_underlying_ino; + info.path = new_path.to_string(); + info.has_extra_reverse |= keeps_origin_reverse; + + if (old_layer, old_underlying_ino) != (new_layer, new_underlying_ino) + && !keeps_origin_reverse + { + self.reverse.remove(&(old_layer, old_underlying_ino)); + } + self.reverse + .insert((new_layer, new_underlying_ino), overlay_ino); + if self.path.get(&old_path).copied() == Some(overlay_ino) { + self.path.remove(&old_path); + } + self.path.insert(new_path.to_string(), overlay_ino); + } + + fn promote_to_delta(&mut self, path: &str, delta_ino: i64) { + let Some(&overlay_ino) = self.path.get(path) else { + return; + }; + let Some(info) = self.inode.get_mut(&overlay_ino) else { + return; + }; + if info.layer == Layer::Base { + let old_base_ino = info.underlying_ino; + info.layer = Layer::Delta; + info.underlying_ino = delta_ino; + info.has_extra_reverse = false; + self.reverse.remove(&(Layer::Base, old_base_ino)); + self.reverse.insert((Layer::Delta, delta_ino), overlay_ino); + } + } + + fn retain_lookup(&mut self, ino: i64, nlookup: u64) { + if ino == ROOT_INO || nlookup == 0 { + return; + } + let refs = self.lookup_refs.entry(ino).or_insert(0); + *refs = refs.saturating_add(nlookup); + } + + fn forget_lookup(&mut self, ino: i64, nlookup: u64) { + if ino == ROOT_INO { + return; + } + + let Some(refs) = self.lookup_refs.get_mut(&ino) else { + return; + }; + if *refs > nlookup { + *refs -= nlookup; + return; + } + + self.lookup_refs.remove(&ino); + self.prune_unreferenced(ino); + } + + fn prune_unreferenced(&mut self, ino: i64) { + let Some(info) = self.inode.remove(&ino) else { + return; + }; + if self.path.get(&info.path).copied() == Some(ino) { + self.path.remove(&info.path); + } + self.reverse.remove(&(info.layer, info.underlying_ino)); + if info.has_extra_reverse { + // Copied-up inodes may also keep a base-origin reverse entry for + // same-session inode stability. Delta-only entries, the common + // clone/write path, avoid the old whole-map scan. + self.reverse.retain(|_, mapped_ino| *mapped_ino != ino); + } + } + + #[cfg(test)] + fn counts(&self) -> OverlayMapCounts { + OverlayMapCounts { + inode_entries: self.inode.len(), + reverse_entries: self.reverse.len(), + path_entries: self.path.len(), + lookup_ref_entries: self.lookup_refs.len(), + } + } +} + +impl OverlayFS { + /// Get or create an overlay inode for a layer inode. + pub(super) fn get_or_create_overlay_ino( + &self, + layer: Layer, + underlying_ino: i64, + path: &str, + ) -> i64 { + self.maps.lock().get_or_create(layer, underlying_ino, path) + } + + /// Refresh an existing overlay inode mapping to point at a new backing inode/path. + pub(super) fn refresh_overlay_mapping( + &self, + overlay_ino: i64, + new_layer: Layer, + new_underlying_ino: i64, + new_path: &str, + ) { + self.maps + .lock() + .refresh(overlay_ino, new_layer, new_underlying_ino, new_path); + } + + /// Get inode info for an overlay inode. + pub(super) fn get_inode_info(&self, ino: i64) -> Option { + self.maps.lock().inode.get(&ino).cloned() + } + + pub(super) fn live_origin_overlay_ino(&self, base_ino: i64, path: &str) -> Option { + let maps = self.maps.lock(); + let overlay_ino = maps.reverse.get(&(Layer::Base, base_ino)).copied()?; + let info = maps.inode.get(&overlay_ino)?; + (info.path == path).then_some(overlay_ino) + } + + /// Build path from parent inode and name. + pub(super) fn build_path(&self, parent_ino: i64, name: &str) -> Result { + let info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; + Ok(if info.path == "/" { + format!("/{}", name) + } else { + format!("{}/{}", info.path, name) + }) + } + + /// Store origin mapping for copy-up + pub(super) async fn add_origin_mapping(&self, delta_ino: i64, base_ino: i64) -> Result<()> { + let conn = self.delta.get_connection().await?; + conn.execute( + "INSERT OR REPLACE INTO fs_origin (delta_ino, base_ino) VALUES (?, ?)", + (delta_ino, base_ino), + ) + .await?; + self.origin_map.write().insert(delta_ino, base_ino); + Ok(()) + } + + /// Get origin inode for a delta inode + pub(super) fn get_origin_ino(&self, delta_ino: i64) -> Option { + self.origin_map.read().get(&delta_ino).copied() + } + + pub(super) fn promote_mapping_to_delta(&self, path: &str, delta_ino: i64) { + self.maps.lock().promote_to_delta(path, delta_ino); + } + + pub(super) fn retain_overlay_lookup(&self, ino: i64, nlookup: u64) { + self.maps.lock().retain_lookup(ino, nlookup); + } + + pub(super) fn forget_overlay_lookup(&self, ino: i64, nlookup: u64) { + self.maps.lock().forget_lookup(ino, nlookup); + } + + #[cfg(test)] + pub(super) fn debug_map_counts(&self) -> OverlayMapCounts { + self.maps.lock().counts() + } +} diff --git a/crates/agentfs-core/src/fs/overlay/mod.rs b/crates/agentfs-core/src/fs/overlay/mod.rs new file mode 100644 index 00000000..89854b17 --- /dev/null +++ b/crates/agentfs-core/src/fs/overlay/mod.rs @@ -0,0 +1,336 @@ +//! Overlay filesystem: base layer + delta DB with whiteouts and origin maps. +//! +//! Lock order: the overlay's sync locks (`maps`, `whiteouts`, `origin_map`, +//! `whiteout_fault`) are leaf locks. Each is taken alone for a short critical +//! section, they never nest with one another, and DB awaits complete before +//! any guard is taken (`clippy::await_holding_lock` is deny-by-workspace). + +mod copyup; +mod fs; +mod maps; +mod partial; +mod whiteouts; + +use crate::error::{Error, Result}; +use crate::schema; +use async_trait::async_trait; +use parking_lot::{Mutex, RwLock}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; +use turso::{Connection, Value}; + +use super::{ + agentfs::{AgentFS, ReapHook}, + BoxedFile, File, FileSystem, Stats, WriteRange, +}; + +use maps::{InodeInfo, Layer, OverlayMaps}; +use partial::{OverlayPartialFile, PartialOrigin}; + +/// Root inode number (matches FUSE convention) +pub(super) const ROOT_INO: i64 = 1; +pub(super) const STORAGE_CHUNKED: i64 = 0; +pub const DEFAULT_PARTIAL_ORIGIN_THRESHOLD_BYTES: u64 = 1024 * 1024; + +/// Explicit policy for partial-origin copy-up of regular base files. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PartialOriginMode { + /// Always use whole-file copy-up. + Off, + /// Use partial-origin copy-up for eligible regular base files. + On, + /// Use partial-origin copy-up for eligible regular base files at or above a threshold. + Auto, +} + +/// Runtime policy controlling when overlay writes may create partial-origin rows. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PartialOriginPolicy { + pub mode: PartialOriginMode, + pub threshold_bytes: u64, +} + +impl Default for PartialOriginPolicy { + fn default() -> Self { + Self { + mode: PartialOriginMode::Off, + threshold_bytes: DEFAULT_PARTIAL_ORIGIN_THRESHOLD_BYTES, + } + } +} + +impl PartialOriginPolicy { + pub fn new(mode: PartialOriginMode) -> Self { + Self { + mode, + ..Self::default() + } + } + + pub fn with_threshold_bytes(mut self, threshold_bytes: u64) -> Self { + self.threshold_bytes = threshold_bytes; + self + } + + fn permits(&self, stats: &Stats) -> bool { + if !stats.is_file() { + return false; + } + + match self.mode { + PartialOriginMode::Off => false, + PartialOriginMode::On => true, + PartialOriginMode::Auto => u64::try_from(stats.size) + .map(|size| size >= self.threshold_bytes) + .unwrap_or(false), + } + } +} + +pub(super) fn current_timestamp() -> Result<(i64, i64)> { + let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; + Ok((dur.as_secs() as i64, dur.subsec_nanos() as i64)) +} + +pub(super) fn is_write_open(flags: i32) -> bool { + (flags & libc::O_ACCMODE) != libc::O_RDONLY || (flags & libc::O_TRUNC) != 0 +} + +pub(super) fn mount_visible_file(inner: BoxedFile, overlay_ino: i64) -> BoxedFile { + Arc::new(MountVisibleFile { inner, overlay_ino }) +} + +struct MountVisibleFile { + inner: BoxedFile, + overlay_ino: i64, +} + +#[async_trait] +impl File for MountVisibleFile { + async fn pread(&self, offset: u64, size: u64) -> Result> { + self.inner.pread(offset, size).await + } + + async fn pwrite(&self, offset: u64, data: &[u8]) -> Result<()> { + self.inner.pwrite(offset, data).await + } + + async fn pwrite_ranges(&self, ranges: Vec) -> Result<()> { + self.inner.pwrite_ranges(ranges).await + } + + async fn pwrite_ranges_batched(&self, ranges: Vec) -> Result<()> { + self.inner.pwrite_ranges_batched(ranges).await + } + + async fn drain_writes(&self) -> Result<()> { + self.inner.drain_writes().await + } + + async fn truncate(&self, size: u64) -> Result<()> { + self.inner.truncate(size).await + } + + async fn fsync(&self) -> Result<()> { + self.inner.fsync().await + } + + async fn fstat(&self) -> Result { + let mut stats = self.inner.fstat().await?; + stats.ino = self.overlay_ino; + Ok(stats) + } +} + +struct OverlaySidecarReapHook; + +#[async_trait] +impl ReapHook for OverlaySidecarReapHook { + fn dedup_key(&self) -> Option<&'static str> { + Some("overlay-sidecar") + } + + async fn on_reap(&self, conn: &Connection, ino: i64) -> Result<()> { + conn.execute("DELETE FROM fs_origin WHERE delta_ino = ?", (ino,)) + .await?; + conn.execute("DELETE FROM fs_chunk_override WHERE delta_ino = ?", (ino,)) + .await?; + conn.execute("DELETE FROM fs_partial_origin WHERE delta_ino = ?", (ino,)) + .await?; + Ok(()) + } +} + +/// A copy-on-write overlay filesystem using inode-based operations. +/// +/// Combines a read-only base layer with a writable delta layer (`AgentFS`). +/// All modifications are written to the delta layer, while reads fall back +/// to the base layer if not found in delta. +/// +/// # Directory opacity +/// +/// This overlay deliberately has no opaque-directory concept. If a base +/// directory is removed through the overlay and later recreated at the same +/// path, base children can resurface on `readdir`, and the base-directory +/// rename guard will still return `EXDEV` while that base path is visible. +/// That diverges from kernel overlayfs, but it is the accepted AgentFS +/// behavior. The `resolves_to_visible_base_directory` signal must stay shared +/// by both readdir base-child merging and the rename guard if opacity is ever +/// revisited. +pub struct OverlayFS { + /// The underlying read-only base filesystem + base: Arc, + /// The delta layer where modifications go + delta: AgentFS, + /// Overlay inode maps, reverse maps, path maps, allocator, and lookup refs. + maps: Mutex, + /// Set of whiteout paths (deleted from base) + whiteouts: RwLock>, + /// Origin mapping: delta_ino -> base_ino (for copy-up consistency) + origin_map: RwLock>, + /// Explicit policy for chunk-granularity base fallback. + partial_origin_policy: PartialOriginPolicy, + /// Test-only fault injection for whiteout transaction rollback coverage. + #[cfg(test)] + whiteout_fault: Mutex>, +} + +impl OverlayFS { + /// Create a new overlay filesystem + pub fn new(base: Arc, delta: AgentFS) -> Self { + let partial_origin_policy = delta.partial_origin_policy(); + Self::new_with_partial_origin_policy(base, delta, partial_origin_policy) + } + + pub fn new_with_partial_origin_policy( + base: Arc, + delta: AgentFS, + partial_origin_policy: PartialOriginPolicy, + ) -> Self { + Self::new_with_partial_origin_policy_inner(base, delta, partial_origin_policy) + } + + #[cfg(test)] + fn new_with_partial_origin( + base: Arc, + delta: AgentFS, + partial_origin_enabled: bool, + ) -> Self { + let mode = if partial_origin_enabled { + PartialOriginMode::On + } else { + PartialOriginMode::Off + }; + Self::new_with_partial_origin_policy_inner(base, delta, PartialOriginPolicy::new(mode)) + } + + fn new_with_partial_origin_policy_inner( + base: Arc, + delta: AgentFS, + partial_origin_policy: PartialOriginPolicy, + ) -> Self { + delta.register_reap_hook(Self::sidecar_reap_hook()); + + Self { + base, + delta, + maps: Mutex::new(OverlayMaps::new()), + whiteouts: RwLock::new(HashSet::new()), + origin_map: RwLock::new(HashMap::new()), + partial_origin_policy, + #[cfg(test)] + whiteout_fault: Mutex::new(None), + } + } + + pub(crate) fn sidecar_reap_hook() -> Arc { + Arc::new(OverlaySidecarReapHook) + } + + /// Initialize the overlay filesystem schema + pub async fn init_schema(conn: &Connection, base_path: &str) -> Result<()> { + schema::set_overlay_base_path(conn, base_path).await + } + + /// Initialize the overlay filesystem + pub async fn init(&self, base_path: &str) -> Result<()> { + let conn = self.delta.get_connection().await?; + Self::init_schema(&conn, base_path).await?; + self.load_whiteouts(&conn).await?; + self.load_origins(&conn).await?; + Ok(()) + } + + /// Load whiteouts from database into memory + async fn load_whiteouts(&self, conn: &Connection) -> Result<()> { + let mut rows = conn.query("SELECT path FROM fs_whiteout", ()).await?; + let mut paths = Vec::new(); + while let Some(row) = rows.next().await? { + if let Some(path) = row.get_value(0).ok().and_then(|v| match v { + Value::Text(s) => Some(s.clone()), + _ => None, + }) { + paths.push(path); + } + } + let mut whiteouts = self.whiteouts.write(); + for path in paths { + whiteouts.insert(path); + } + Ok(()) + } + + /// Load existing whiteouts (public interface) + pub async fn load_whiteouts_public(&self) -> Result<()> { + let conn = self.delta.get_connection().await?; + self.load_whiteouts(&conn).await + } + + /// Load persisted state (whiteouts and origin mappings) from database. + /// Call this after creating an OverlayFS for an existing database. + pub async fn load(&self) -> Result<()> { + let conn = self.delta.get_connection().await?; + self.load_whiteouts(&conn).await?; + self.load_origins(&conn).await?; + Ok(()) + } + + /// Load origin mappings from database. + async fn load_origins(&self, conn: &Connection) -> Result<()> { + let mut rows = conn + .query("SELECT delta_ino, base_ino FROM fs_origin", ()) + .await?; + let mut mappings = Vec::new(); + while let Some(row) = rows.next().await? { + let delta_ino = row.get_value(0).ok().and_then(|v| v.as_integer().copied()); + let base_ino = row.get_value(1).ok().and_then(|v| v.as_integer().copied()); + if let (Some(d), Some(b)) = (delta_ino, base_ino) { + mappings.push((d, b)); + } + } + let mut origins = self.origin_map.write(); + for (d, b) in mappings { + origins.insert(d, b); + } + Ok(()) + } + + /// Get a reference to the base layer + pub fn base(&self) -> &Arc { + &self.base + } + + /// Get a reference to the delta layer + pub fn delta(&self) -> &AgentFS { + &self.delta + } +} + +#[cfg(all(test, any(target_os = "linux", target_os = "macos")))] +// Keep the extracted test body byte-for-byte; this feature is a pure move. +#[rustfmt::skip] +#[path = "tests.rs"] +mod overlay_tests; diff --git a/crates/agentfs-core/src/fs/overlay/partial.rs b/crates/agentfs-core/src/fs/overlay/partial.rs new file mode 100644 index 00000000..2afabd5e --- /dev/null +++ b/crates/agentfs-core/src/fs/overlay/partial.rs @@ -0,0 +1,458 @@ +use super::super::agentfs::store::{self, ChunkWriteHooks, WriteRangeRef}; +use super::*; +use crate::fs::{File, FsError, WriteRange}; +use std::sync::Arc; +use turso::transaction::{Transaction, TransactionBehavior}; + +#[derive(Debug, Clone)] +pub(super) struct PartialOrigin { + pub(super) base_path: String, + pub(super) base_fingerprint_size: i64, + pub(super) base_mtime: i64, + pub(super) base_mtime_nsec: u32, + pub(super) base_ctime: i64, + pub(super) base_ctime_nsec: u32, +} + +pub(super) struct OverlayPartialFile { + pub(super) delta: AgentFS, + pub(super) base: Arc, + pub(super) base_file: super::super::BoxedFile, + pub(super) origin: PartialOrigin, + pub(super) overlay_ino: i64, + pub(super) delta_ino: i64, + pub(super) chunk_size: usize, +} + +struct PartialOriginChunkHooks<'a> { + file: &'a OverlayPartialFile, +} + +#[async_trait] +impl ChunkWriteHooks for PartialOriginChunkHooks<'_> { + async fn seed_missing_chunk( + &self, + conn: &Connection, + ino: i64, + geometry: crate::config::Geometry, + chunk_index: i64, + ) -> Result>> { + debug_assert_eq!(ino, self.file.delta_ino); + let chunk_index = u64::try_from(chunk_index) + .map_err(|_| Error::Internal("negative chunk index".to_string()))?; + let base_size = self.file.partial_base_size_with_conn(conn).await?; + let chunk_start = chunk_index + .checked_mul(geometry.chunk_size as u64) + .ok_or_else(|| Error::Internal("chunk offset overflow".to_string()))?; + if chunk_start >= base_size { + return Ok(None); + } + + self.file.validate_current_origin().await?; + let readable = std::cmp::min(geometry.chunk_size as u64, base_size - chunk_start); + let mut chunk = self.file.base_file.pread(chunk_start, readable).await?; + chunk.resize(geometry.chunk_size, 0); + Ok(Some(chunk)) + } + + async fn chunk_written(&self, conn: &Connection, ino: i64, chunk_index: i64) -> Result<()> { + debug_assert_eq!(ino, self.file.delta_ino); + conn.execute( + "INSERT OR IGNORE INTO fs_chunk_override (delta_ino, chunk_index) VALUES (?, ?)", + (ino, chunk_index), + ) + .await?; + Ok(()) + } +} + +impl OverlayFS { + pub(super) async fn partial_origin_for_delta( + &self, + delta_ino: i64, + ) -> Result> { + let conn = self.delta.get_connection().await?; + let mut rows = conn + .query( + "SELECT base_path, base_size, base_fingerprint_size, + base_mtime, base_mtime_nsec, base_ctime, base_ctime_nsec + FROM fs_partial_origin WHERE delta_ino = ?", + (delta_ino,), + ) + .await?; + if let Some(row) = rows.next().await? { + let base_path = match row.get_value(0)? { + Value::Text(path) => path, + _ => { + return Err(Error::Internal( + "invalid partial origin base_path".to_string(), + )) + } + }; + let base_size = row + .get_value(1) + .ok() + .and_then(|v| v.as_integer().copied()) + .ok_or_else(|| Error::Internal("invalid partial origin base_size".to_string()))?; + let base_fingerprint_size = row + .get_value(2) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(base_size); + let base_mtime = row + .get_value(3) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(0); + let base_mtime_nsec = row + .get_value(4) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(0) as u32; + let base_ctime = row + .get_value(5) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(0); + let base_ctime_nsec = row + .get_value(6) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(0) as u32; + Ok(Some(PartialOrigin { + base_path, + base_fingerprint_size: if base_fingerprint_size < 0 { + base_size + } else { + base_fingerprint_size + }, + base_mtime, + base_mtime_nsec, + base_ctime, + base_ctime_nsec, + })) + } else { + Ok(None) + } + } + + pub(super) async fn add_partial_origin_mapping( + &self, + delta_ino: i64, + base_ino: i64, + base_path: &str, + base_stats: &Stats, + ) -> Result<()> { + let conn = self.delta.get_connection().await?; + let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64; + conn.execute( + "INSERT OR REPLACE INTO fs_partial_origin ( + delta_ino, base_ino, base_path, base_size, created_at + ) VALUES (?1, ?2, ?3, ?4, ?5)", + (delta_ino, base_ino, base_path, base_stats.size, now), + ) + .await?; + conn.execute( + "UPDATE fs_partial_origin + SET base_fingerprint_size = ?1, base_mtime = ?2, base_mtime_nsec = ?3 + WHERE delta_ino = ?4", + ( + base_stats.size, + base_stats.mtime, + base_stats.mtime_nsec as i64, + delta_ino, + ), + ) + .await?; + conn.execute( + "UPDATE fs_partial_origin + SET base_ctime = ?1, base_ctime_nsec = ?2 + WHERE delta_ino = ?3", + (base_stats.ctime, base_stats.ctime_nsec as i64, delta_ino), + ) + .await?; + Ok(()) + } +} + +#[async_trait] +impl File for OverlayPartialFile { + async fn pread(&self, offset: u64, size: u64) -> Result> { + self.validate_current_origin().await?; + let conn = self.delta.get_connection().await?; + let file_size = self.delta_file_size_with_conn(&conn).await?; + if offset >= file_size || size == 0 { + return Ok(Vec::new()); + } + + let read_len = std::cmp::min(size, file_size - offset) as usize; + let chunk_size = self.chunk_size as u64; + let mut result = Vec::with_capacity(read_len); + + while result.len() < read_len { + let current_offset = offset + result.len() as u64; + let chunk_index = current_offset / chunk_size; + let offset_in_chunk = (current_offset % chunk_size) as usize; + let take = std::cmp::min( + self.chunk_size - offset_in_chunk, + read_len.saturating_sub(result.len()), + ); + + let chunk = self.read_merged_chunk_with_conn(&conn, chunk_index).await?; + result.extend_from_slice(&chunk[offset_in_chunk..offset_in_chunk + take]); + } + + Ok(result) + } + + async fn pwrite(&self, offset: u64, data: &[u8]) -> Result<()> { + if data.is_empty() { + return Ok(()); + } + self.pwrite_ranges(vec![WriteRange { + offset, + data: data.to_vec(), + }]) + .await + } + + async fn pwrite_ranges(&self, ranges: Vec) -> Result<()> { + if ranges.iter().all(|range| range.data.is_empty()) { + return Ok(()); + } + let conn = self.delta.get_connection().await?; + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let range_refs: Vec<_> = ranges + .iter() + .map(|range| WriteRangeRef { + offset: range.offset, + data: range.data.as_slice(), + }) + .collect(); + let hooks = PartialOriginChunkHooks { file: self }; + + let result: Result<()> = async { + store::write_ranges_with_chunk_hooks( + &conn, + self.delta_ino, + self.geometry(), + &range_refs, + &hooks, + ) + .await?; + Ok(()) + } + .await; + + match result { + Ok(()) => { + txn.commit().await?; + self.delta.invalidate_attr(self.delta_ino); + Ok(()) + } + Err(e) => { + let _ = txn.rollback().await; + Err(e) + } + } + } + + async fn truncate(&self, size: u64) -> Result<()> { + let conn = self.delta.get_connection().await?; + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let hooks = PartialOriginChunkHooks { file: self }; + + let result: Result<()> = async { + store::truncate_with_chunk_hooks(&conn, self.delta_ino, self.geometry(), size, &hooks) + .await?; + self.prune_chunk_overrides_after_truncate(&conn, size) + .await?; + + let origin_base_size = self.partial_base_size_with_conn(&conn).await?; + if size < origin_base_size { + conn.execute( + "UPDATE fs_partial_origin SET base_size = ? WHERE delta_ino = ?", + (size as i64, self.delta_ino), + ) + .await?; + } + Ok(()) + } + .await; + + match result { + Ok(()) => { + txn.commit().await?; + self.delta.invalidate_attr(self.delta_ino); + Ok(()) + } + Err(e) => { + let _ = txn.rollback().await; + Err(e) + } + } + } + + async fn fsync(&self) -> Result<()> { + self.delta.fsync().await + } + + async fn fstat(&self) -> Result { + let mut stats = FileSystem::getattr(&self.delta, self.delta_ino) + .await? + .ok_or(FsError::NotFound)?; + stats.ino = self.overlay_ino; + Ok(stats) + } +} + +impl OverlayPartialFile { + fn geometry(&self) -> crate::config::Geometry { + crate::config::Geometry { + chunk_size: self.chunk_size, + inline_threshold: self.delta.inline_threshold(), + } + } + + async fn resolve_origin_base_stats(&self) -> Result> { + let mut ino = ROOT_INO; + if self.origin.base_path == "/" { + return self.base.getattr(ino).await; + } + + let mut stats = None; + for component in self.origin.base_path.split('/').filter(|s| !s.is_empty()) { + let Some(next) = self.base.lookup(ino, component).await? else { + return Ok(None); + }; + ino = next.ino; + stats = Some(next); + } + Ok(stats) + } + + async fn validate_current_origin(&self) -> Result<()> { + let stats = self + .resolve_origin_base_stats() + .await? + .ok_or(FsError::NotFound)?; + if stats.size != self.origin.base_fingerprint_size + || stats.mtime != self.origin.base_mtime + || stats.mtime_nsec != self.origin.base_mtime_nsec + || stats.ctime != self.origin.base_ctime + || stats.ctime_nsec != self.origin.base_ctime_nsec + { + return Err(Error::Internal(format!( + "partial-origin base changed for {}", + self.origin.base_path + ))); + } + Ok(()) + } + + async fn delta_file_size_with_conn(&self, conn: &Connection) -> Result { + let mut rows = conn + .query("SELECT size FROM fs_inode WHERE ino = ?", (self.delta_ino,)) + .await?; + if let Some(row) = rows.next().await? { + Ok(row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(0) as u64) + } else { + Err(FsError::NotFound.into()) + } + } + + async fn prune_chunk_overrides_after_truncate( + &self, + conn: &Connection, + size: u64, + ) -> Result<()> { + if size == 0 { + conn.execute( + "DELETE FROM fs_chunk_override WHERE delta_ino = ?", + (self.delta_ino,), + ) + .await?; + return Ok(()); + } + + let last_chunk = (size - 1) / self.chunk_size as u64; + conn.execute( + "DELETE FROM fs_chunk_override WHERE delta_ino = ? AND chunk_index > ?", + (self.delta_ino, last_chunk as i64), + ) + .await?; + Ok(()) + } + + async fn partial_base_size_with_conn(&self, conn: &Connection) -> Result { + let mut rows = conn + .query( + "SELECT base_size FROM fs_partial_origin WHERE delta_ino = ?", + (self.delta_ino,), + ) + .await?; + if let Some(row) = rows.next().await? { + Ok(row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .unwrap_or(0) as u64) + } else { + Err(FsError::NotFound.into()) + } + } + + async fn chunk_is_override_with_conn( + &self, + conn: &Connection, + chunk_index: u64, + ) -> Result { + let mut rows = conn + .query( + "SELECT 1 FROM fs_chunk_override WHERE delta_ino = ? AND chunk_index = ?", + (self.delta_ino, chunk_index as i64), + ) + .await?; + Ok(rows.next().await?.is_some()) + } + + async fn read_merged_chunk_with_conn( + &self, + conn: &Connection, + chunk_index: u64, + ) -> Result> { + if self.chunk_is_override_with_conn(conn, chunk_index).await? { + let chunk_start = chunk_index + .checked_mul(self.chunk_size as u64) + .ok_or_else(|| Error::Internal("chunk offset overflow".to_string()))?; + let mut chunk = store::read( + conn, + self.delta_ino, + self.geometry(), + chunk_start, + self.chunk_size as u64, + ) + .await?; + chunk.resize(self.chunk_size, 0); + return Ok(chunk); + } + + let base_size = self.partial_base_size_with_conn(conn).await?; + let chunk_start = chunk_index + .checked_mul(self.chunk_size as u64) + .ok_or_else(|| Error::Internal("chunk offset overflow".to_string()))?; + let mut chunk = if chunk_start < base_size { + self.validate_current_origin().await?; + let readable = std::cmp::min(self.chunk_size as u64, base_size - chunk_start); + self.base_file.pread(chunk_start, readable).await? + } else { + Vec::new() + }; + chunk.resize(self.chunk_size, 0); + Ok(chunk) + } +} diff --git a/sdk/rust/src/filesystem/overlayfs.rs b/crates/agentfs-core/src/fs/overlay/tests.rs similarity index 50% rename from sdk/rust/src/filesystem/overlayfs.rs rename to crates/agentfs-core/src/fs/overlay/tests.rs index e5b4fda6..1e03db3e 100644 --- a/sdk/rust/src/filesystem/overlayfs.rs +++ b/crates/agentfs-core/src/fs/overlay/tests.rs @@ -1,2507 +1,8 @@ -use crate::error::{Error, Result}; -use async_trait::async_trait; -use std::{ - collections::{HashMap, HashSet}, - sync::{ - atomic::{AtomicI64, Ordering}, - Arc, Mutex, RwLock, - }, - time::{SystemTime, UNIX_EPOCH}, -}; -use tracing::trace; -use turso::transaction::{Transaction, TransactionBehavior}; -use turso::{Connection, Value}; - -use super::{ - agentfs::AgentFS, BoxedFile, DirEntry, File, FileSystem, FilesystemStats, FsError, Stats, - TimeChange, WriteRange, -}; - -/// Root inode number (matches FUSE convention) -const ROOT_INO: i64 = 1; -const STORAGE_CHUNKED: i64 = 0; -const PARTIAL_ORIGIN_ENV: &str = "AGENTFS_OVERLAY_PARTIAL_ORIGIN"; -pub const DEFAULT_PARTIAL_ORIGIN_THRESHOLD_BYTES: u64 = 1024 * 1024; - -/// Explicit policy for partial-origin copy-up of regular base files. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PartialOriginMode { - /// Always use whole-file copy-up. - Off, - /// Use partial-origin copy-up for eligible regular base files. - On, - /// Use partial-origin copy-up for eligible regular base files at or above a threshold. - Auto, -} - -/// Runtime policy controlling when overlay writes may create partial-origin rows. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PartialOriginPolicy { - pub mode: PartialOriginMode, - pub threshold_bytes: u64, -} - -impl Default for PartialOriginPolicy { - fn default() -> Self { - Self { - mode: PartialOriginMode::Off, - threshold_bytes: DEFAULT_PARTIAL_ORIGIN_THRESHOLD_BYTES, - } - } -} - -impl PartialOriginPolicy { - pub fn new(mode: PartialOriginMode) -> Self { - Self { - mode, - ..Self::default() - } - } - - pub fn with_threshold_bytes(mut self, threshold_bytes: u64) -> Self { - self.threshold_bytes = threshold_bytes; - self - } - - /// Preserve legacy env-var opt-in while keeping ordinary defaults strict/off. - pub fn from_env_compat() -> Self { - if env_flag_enabled(PARTIAL_ORIGIN_ENV) { - Self::new(PartialOriginMode::On) - } else { - Self::default() - } - } - - fn permits(&self, stats: &Stats) -> bool { - if !stats.is_file() { - return false; - } - - match self.mode { - PartialOriginMode::Off => false, - PartialOriginMode::On => true, - PartialOriginMode::Auto => u64::try_from(stats.size) - .map(|size| size >= self.threshold_bytes) - .unwrap_or(false), - } - } -} - -fn current_timestamp() -> Result<(i64, i64)> { - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - Ok((dur.as_secs() as i64, dur.subsec_nanos() as i64)) -} - -fn env_flag_enabled(name: &str) -> bool { - std::env::var(name) - .map(|value| { - matches!( - value.to_ascii_lowercase().as_str(), - "1" | "true" | "yes" | "on" - ) - }) - .unwrap_or(false) -} - -fn is_write_open(flags: i32) -> bool { - (flags & libc::O_ACCMODE) != libc::O_RDONLY || (flags & libc::O_TRUNC) != 0 -} - -fn parent_path_for_whiteout(path: &str) -> String { - if path == "/" { - return "/".to_string(); - } - - let trimmed = path.trim_end_matches('/'); - match trimmed.rfind('/') { - Some(0) | None => "/".to_string(), - Some(index) => trimmed[..index].to_string(), - } -} - -/// Which layer an inode belongs to -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum Layer { - Delta, - Base, -} - -/// Information about an inode in the overlay filesystem -#[derive(Debug, Clone)] -struct InodeInfo { - /// Which layer this inode lives in - layer: Layer, - /// The inode number in the underlying layer - underlying_ino: i64, - /// Virtual path (for whiteout and copy-up operations) - path: String, -} - -#[derive(Debug, Clone)] -struct PartialOrigin { - base_path: String, - base_fingerprint_size: i64, - base_mtime: i64, - base_mtime_nsec: u32, - base_ctime: i64, - base_ctime_nsec: u32, -} - -struct OverlayPartialFile { - delta: AgentFS, - base: Arc, - base_file: BoxedFile, - origin: PartialOrigin, - overlay_ino: i64, - delta_ino: i64, - chunk_size: usize, -} - -/// A copy-on-write overlay filesystem using inode-based operations. -/// -/// Combines a read-only base layer with a writable delta layer (AgentFS). -/// All modifications are written to the delta layer, while reads fall back -/// to the base layer if not found in delta. -pub struct OverlayFS { - /// The underlying read-only base filesystem - base: Arc, - /// The delta layer where modifications go - delta: AgentFS, - /// Map from overlay inode to underlying layer info - inode_map: RwLock>, - /// Reverse map: (layer, underlying_ino) -> overlay_ino - reverse_map: RwLock>, - /// Map from path to overlay inode (for path-based operations) - path_map: RwLock>, - /// Serializes multi-map overlay inode updates. - map_lock: Mutex<()>, - /// Next inode number to allocate - next_ino: AtomicI64, - /// Set of whiteout paths (deleted from base) - whiteouts: RwLock>, - /// Origin mapping: delta_ino -> base_ino (for copy-up consistency) - origin_map: RwLock>, - /// Explicit policy for chunk-granularity base fallback. - partial_origin_policy: PartialOriginPolicy, -} - -impl OverlayFS { - /// Create a new overlay filesystem - pub fn new(base: Arc, delta: AgentFS) -> Self { - Self::new_with_partial_origin_policy(base, delta, PartialOriginPolicy::from_env_compat()) - } - - pub fn new_with_partial_origin_policy( - base: Arc, - delta: AgentFS, - partial_origin_policy: PartialOriginPolicy, - ) -> Self { - Self::new_with_partial_origin_policy_inner(base, delta, partial_origin_policy) - } - - #[cfg(test)] - fn new_with_partial_origin( - base: Arc, - delta: AgentFS, - partial_origin_enabled: bool, - ) -> Self { - let mode = if partial_origin_enabled { - PartialOriginMode::On - } else { - PartialOriginMode::Off - }; - Self::new_with_partial_origin_policy_inner(base, delta, PartialOriginPolicy::new(mode)) - } - - fn new_with_partial_origin_policy_inner( - base: Arc, - delta: AgentFS, - partial_origin_policy: PartialOriginPolicy, - ) -> Self { - let mut inode_map = HashMap::new(); - let mut reverse_map = HashMap::new(); - let mut path_map = HashMap::new(); - - // Root inode maps to delta's root (inode 1) - inode_map.insert( - ROOT_INO, - InodeInfo { - layer: Layer::Delta, - underlying_ino: 1, - path: "/".to_string(), - }, - ); - reverse_map.insert((Layer::Delta, 1), ROOT_INO); - path_map.insert("/".to_string(), ROOT_INO); - - Self { - base, - delta, - inode_map: RwLock::new(inode_map), - reverse_map: RwLock::new(reverse_map), - path_map: RwLock::new(path_map), - map_lock: Mutex::new(()), - next_ino: AtomicI64::new(2), - whiteouts: RwLock::new(HashSet::new()), - origin_map: RwLock::new(HashMap::new()), - partial_origin_policy, - } - } - - /// Initialize the overlay filesystem schema - pub async fn init_schema(conn: &Connection, base_path: &str) -> Result<()> { - conn.execute( - "CREATE TABLE IF NOT EXISTS fs_whiteout ( - path TEXT PRIMARY KEY, - parent_path TEXT NOT NULL, - created_at INTEGER NOT NULL - )", - (), - ) - .await?; - Self::ensure_whiteout_parent_path(conn).await?; - conn.execute( - "CREATE INDEX IF NOT EXISTS idx_fs_whiteout_parent ON fs_whiteout(parent_path)", - (), - ) - .await?; - conn.execute( - "CREATE TABLE IF NOT EXISTS fs_overlay_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL - )", - (), - ) - .await?; - conn.execute( - "INSERT OR REPLACE INTO fs_overlay_config (key, value) VALUES ('base_path', ?1)", - [Value::Text(base_path.to_string())], - ) - .await?; - conn.execute( - "CREATE TABLE IF NOT EXISTS fs_origin ( - delta_ino INTEGER PRIMARY KEY, - base_ino INTEGER NOT NULL - )", - (), - ) - .await?; - conn.execute( - "CREATE TABLE IF NOT EXISTS fs_partial_origin ( - delta_ino INTEGER PRIMARY KEY, - base_ino INTEGER NOT NULL, - base_path TEXT NOT NULL, - base_size INTEGER NOT NULL, - base_fingerprint_size INTEGER NOT NULL DEFAULT -1, - base_mtime INTEGER NOT NULL DEFAULT 0, - base_mtime_nsec INTEGER NOT NULL DEFAULT 0, - base_ctime INTEGER NOT NULL DEFAULT 0, - base_ctime_nsec INTEGER NOT NULL DEFAULT 0, - created_at INTEGER NOT NULL - )", - (), - ) - .await?; - conn.execute( - "ALTER TABLE fs_partial_origin ADD COLUMN base_fingerprint_size INTEGER NOT NULL DEFAULT -1", - (), - ) - .await - .ok(); - conn.execute( - "ALTER TABLE fs_partial_origin ADD COLUMN base_mtime INTEGER NOT NULL DEFAULT 0", - (), - ) - .await - .ok(); - conn.execute( - "ALTER TABLE fs_partial_origin ADD COLUMN base_mtime_nsec INTEGER NOT NULL DEFAULT 0", - (), - ) - .await - .ok(); - conn.execute( - "ALTER TABLE fs_partial_origin ADD COLUMN base_ctime INTEGER NOT NULL DEFAULT 0", - (), - ) - .await - .ok(); - conn.execute( - "ALTER TABLE fs_partial_origin ADD COLUMN base_ctime_nsec INTEGER NOT NULL DEFAULT 0", - (), - ) - .await - .ok(); - conn.execute( - "CREATE TABLE IF NOT EXISTS fs_chunk_override ( - delta_ino INTEGER NOT NULL, - chunk_index INTEGER NOT NULL, - PRIMARY KEY (delta_ino, chunk_index) - )", - (), - ) - .await?; - Ok(()) - } - - async fn ensure_whiteout_parent_path(conn: &Connection) -> Result<()> { - let mut rows = conn.query("PRAGMA table_info(fs_whiteout)", ()).await?; - let mut has_parent_path = false; - while let Some(row) = rows.next().await? { - if let Some(name) = row.get_value(1).ok().and_then(|value| match value { - Value::Text(name) => Some(name.clone()), - _ => None, - }) { - if name == "parent_path" { - has_parent_path = true; - break; - } - } - } - - if !has_parent_path { - conn.execute( - "ALTER TABLE fs_whiteout ADD COLUMN parent_path TEXT NOT NULL DEFAULT '/'", - (), - ) - .await?; - let mut rows = conn.query("SELECT path FROM fs_whiteout", ()).await?; - let mut paths = Vec::new(); - while let Some(row) = rows.next().await? { - if let Some(path) = row.get_value(0).ok().and_then(|value| match value { - Value::Text(path) => Some(path.clone()), - _ => None, - }) { - paths.push(path); - } - } - for path in paths { - conn.execute( - "UPDATE fs_whiteout SET parent_path = ? WHERE path = ?", - (parent_path_for_whiteout(&path), path), - ) - .await?; - } - } - - Ok(()) - } - - /// Initialize the overlay filesystem - pub async fn init(&self, base_path: &str) -> Result<()> { - let conn = self.delta.get_connection().await?; - Self::init_schema(&conn, base_path).await?; - self.load_whiteouts(&conn).await?; - self.load_origins(&conn).await?; - Ok(()) - } - - /// Load whiteouts from database into memory - async fn load_whiteouts(&self, conn: &Connection) -> Result<()> { - let mut rows = conn.query("SELECT path FROM fs_whiteout", ()).await?; - let mut paths = Vec::new(); - while let Some(row) = rows.next().await? { - if let Some(path) = row.get_value(0).ok().and_then(|v| match v { - Value::Text(s) => Some(s.clone()), - _ => None, - }) { - paths.push(path); - } - } - let mut whiteouts = self.whiteouts.write().unwrap(); - for path in paths { - whiteouts.insert(path); - } - Ok(()) - } - - /// Load existing whiteouts (public interface) - pub async fn load_whiteouts_public(&self) -> Result<()> { - let conn = self.delta.get_connection().await?; - self.load_whiteouts(&conn).await - } - - /// Load persisted state (whiteouts and origin mappings) from database. - /// Call this after creating an OverlayFS for an existing database. - pub async fn load(&self) -> Result<()> { - let conn = self.delta.get_connection().await?; - self.load_whiteouts(&conn).await?; - self.load_origins(&conn).await?; - Ok(()) - } - - /// Load origin mappings from database - async fn load_origins(&self, conn: &Connection) -> Result<()> { - let result = conn - .query("SELECT delta_ino, base_ino FROM fs_origin", ()) - .await; - if let Ok(mut rows) = result { - let mut mappings = Vec::new(); - while let Some(row) = rows.next().await? { - let delta_ino = row.get_value(0).ok().and_then(|v| v.as_integer().copied()); - let base_ino = row.get_value(1).ok().and_then(|v| v.as_integer().copied()); - if let (Some(d), Some(b)) = (delta_ino, base_ino) { - mappings.push((d, b)); - } - } - let mut origins = self.origin_map.write().unwrap(); - for (d, b) in mappings { - origins.insert(d, b); - } - } - Ok(()) - } - - /// Check if a path is whiteout (deleted from base) - fn is_whiteout(&self, path: &str) -> bool { - let whiteouts = self.whiteouts.read().unwrap(); - // Check path and all ancestors - let mut current = String::new(); - for component in path.split('/').filter(|s| !s.is_empty()) { - current = format!("{}/{}", current, component); - if whiteouts.contains(¤t) { - return true; - } - } - false - } - - /// Create a whiteout for a path - async fn create_whiteout(&self, path: &str) -> Result<()> { - let conn = self.delta.get_connection().await?; - let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64; - let parent_path = parent_path_for_whiteout(path); - conn.execute( - "INSERT OR REPLACE INTO fs_whiteout (path, parent_path, created_at) VALUES (?, ?, ?)", - (path, parent_path, now), - ) - .await?; - self.whiteouts.write().unwrap().insert(path.to_string()); - Ok(()) - } - - /// Remove a whiteout - async fn remove_whiteout(&self, path: &str) -> Result<()> { - if !self.whiteouts.read().unwrap().contains(path) { - return Ok(()); - } - let conn = self.delta.get_connection().await?; - conn.execute("DELETE FROM fs_whiteout WHERE path = ?", (path,)) - .await?; - self.whiteouts.write().unwrap().remove(path); - Ok(()) - } - - /// Get child whiteouts for a directory - fn get_child_whiteouts(&self, dir_path: &str) -> HashSet { - let whiteouts = self.whiteouts.read().unwrap(); - let prefix = if dir_path == "/" { - "/".to_string() - } else { - format!("{}/", dir_path) - }; - whiteouts - .iter() - .filter_map(|p| { - if dir_path == "/" { - // Direct children of root - let trimmed = p.trim_start_matches('/'); - if !trimmed.contains('/') { - Some(trimmed.to_string()) - } else { - None - } - } else if p.starts_with(&prefix) { - let rest = &p[prefix.len()..]; - if !rest.contains('/') { - Some(rest.to_string()) - } else { - None - } - } else { - None - } - }) - .collect() - } - - /// Allocate a new overlay inode number - fn alloc_ino(&self) -> i64 { - self.next_ino.fetch_add(1, Ordering::Relaxed) - } - - /// Get or create an overlay inode for a layer inode - fn get_or_create_overlay_ino(&self, layer: Layer, underlying_ino: i64, path: &str) -> i64 { - let _map_guard = self.map_lock.lock().unwrap(); - // Check reverse map first - { - let reverse = self.reverse_map.read().unwrap(); - if let Some(&ino) = reverse.get(&(layer, underlying_ino)) { - return ino; - } - } - - // Allocate new inode - let ino = self.alloc_ino(); - { - let mut inode_map = self.inode_map.write().unwrap(); - inode_map.insert( - ino, - InodeInfo { - layer, - underlying_ino, - path: path.to_string(), - }, - ); - } - { - let mut reverse = self.reverse_map.write().unwrap(); - reverse.insert((layer, underlying_ino), ino); - } - { - let mut path_map = self.path_map.write().unwrap(); - path_map.insert(path.to_string(), ino); - } - - ino - } - - /// Refresh an existing overlay inode mapping to point at a new backing inode/path. - /// - /// This is used when we intentionally reuse an existing overlay inode number - /// (for stability), but the underlying layer/path has changed (for example after - /// a base file is copied-up and then renamed in delta). - fn refresh_overlay_mapping( - &self, - overlay_ino: i64, - new_layer: Layer, - new_underlying_ino: i64, - new_path: &str, - ) { - let _map_guard = self.map_lock.lock().unwrap(); - let old_path = { - let mut inode_map = self.inode_map.write().unwrap(); - let Some(info) = inode_map.get_mut(&overlay_ino) else { - return; - }; - let old_path = info.path.clone(); - info.layer = new_layer; - info.underlying_ino = new_underlying_ino; - info.path = new_path.to_string(); - old_path - }; - - { - let mut reverse = self.reverse_map.write().unwrap(); - reverse.insert((new_layer, new_underlying_ino), overlay_ino); - } - - { - let mut path_map = self.path_map.write().unwrap(); - if path_map.get(&old_path).copied() == Some(overlay_ino) { - path_map.remove(&old_path); - } - path_map.insert(new_path.to_string(), overlay_ino); - } - } - - /// Get inode info for an overlay inode - fn get_inode_info(&self, ino: i64) -> Option { - self.inode_map.read().unwrap().get(&ino).cloned() - } - - fn live_origin_overlay_ino(&self, base_ino: i64, path: &str) -> Option { - let overlay_ino = { - let reverse = self.reverse_map.read().unwrap(); - reverse.get(&(Layer::Base, base_ino)).copied()? - }; - let info = self.get_inode_info(overlay_ino)?; - if info.path == path { - Some(overlay_ino) - } else { - None - } - } - - /// Build path from parent inode and name - fn build_path(&self, parent_ino: i64, name: &str) -> Result { - let info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; - Ok(if info.path == "/" { - format!("/{}", name) - } else { - format!("{}/{}", info.path, name) - }) - } - - /// Get a reference to the base layer - pub fn base(&self) -> &Arc { - &self.base - } - - /// Get a reference to the delta layer - pub fn delta(&self) -> &AgentFS { - &self.delta - } - - /// Store origin mapping for copy-up - async fn add_origin_mapping(&self, delta_ino: i64, base_ino: i64) -> Result<()> { - let conn = self.delta.get_connection().await?; - conn.execute( - "INSERT OR REPLACE INTO fs_origin (delta_ino, base_ino) VALUES (?, ?)", - (delta_ino, base_ino), - ) - .await?; - self.origin_map.write().unwrap().insert(delta_ino, base_ino); - Ok(()) - } - - /// Get origin inode for a delta inode - fn get_origin_ino(&self, delta_ino: i64) -> Option { - self.origin_map.read().unwrap().get(&delta_ino).copied() - } - - async fn partial_origin_for_delta(&self, delta_ino: i64) -> Result> { - let conn = self.delta.get_connection().await?; - let mut rows = conn - .query( - "SELECT base_path, base_size, base_fingerprint_size, - base_mtime, base_mtime_nsec, base_ctime, base_ctime_nsec - FROM fs_partial_origin WHERE delta_ino = ?", - (delta_ino,), - ) - .await?; - if let Some(row) = rows.next().await? { - let base_path = match row.get_value(0)? { - Value::Text(path) => path, - _ => { - return Err(Error::Internal( - "invalid partial origin base_path".to_string(), - )) - } - }; - let base_size = row - .get_value(1) - .ok() - .and_then(|v| v.as_integer().copied()) - .ok_or_else(|| Error::Internal("invalid partial origin base_size".to_string()))?; - let base_fingerprint_size = row - .get_value(2) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(base_size); - let base_mtime = row - .get_value(3) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - let base_mtime_nsec = row - .get_value(4) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32; - let base_ctime = row - .get_value(5) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - let base_ctime_nsec = row - .get_value(6) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32; - Ok(Some(PartialOrigin { - base_path, - base_fingerprint_size: if base_fingerprint_size < 0 { - base_size - } else { - base_fingerprint_size - }, - base_mtime, - base_mtime_nsec, - base_ctime, - base_ctime_nsec, - })) - } else { - Ok(None) - } - } - - async fn add_partial_origin_mapping( - &self, - delta_ino: i64, - base_ino: i64, - base_path: &str, - base_stats: &Stats, - ) -> Result<()> { - let conn = self.delta.get_connection().await?; - let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64; - conn.execute( - "INSERT OR REPLACE INTO fs_partial_origin ( - delta_ino, base_ino, base_path, base_size, created_at - ) VALUES (?1, ?2, ?3, ?4, ?5)", - (delta_ino, base_ino, base_path, base_stats.size, now), - ) - .await?; - conn.execute( - "UPDATE fs_partial_origin - SET base_fingerprint_size = ?1, base_mtime = ?2, base_mtime_nsec = ?3 - WHERE delta_ino = ?4", - ( - base_stats.size, - base_stats.mtime, - base_stats.mtime_nsec as i64, - delta_ino, - ), - ) - .await?; - conn.execute( - "UPDATE fs_partial_origin - SET base_ctime = ?1, base_ctime_nsec = ?2 - WHERE delta_ino = ?3", - (base_stats.ctime, base_stats.ctime_nsec as i64, delta_ino), - ) - .await?; - Ok(()) - } - - async fn resolve_base_path(&self, path: &str) -> Result> { - let mut ino = ROOT_INO; - if path == "/" { - return self.base.getattr(ino).await; - } - - let mut stats = None; - for component in path.split('/').filter(|s| !s.is_empty()) { - let Some(next) = self.base.lookup(ino, component).await? else { - return Ok(None); - }; - ino = next.ino; - stats = Some(next); - } - Ok(stats) - } - - fn validate_partial_origin(&self, origin: &PartialOrigin, stats: &Stats) -> Result<()> { - if stats.size != origin.base_fingerprint_size { - return Err(Error::Internal(format!( - "partial-origin base changed for {} (stored size={}, current size={})", - origin.base_path, origin.base_fingerprint_size, stats.size - ))); - } - if stats.mtime != origin.base_mtime - || stats.mtime_nsec != origin.base_mtime_nsec - || stats.ctime != origin.base_ctime - || stats.ctime_nsec != origin.base_ctime_nsec - { - return Err(Error::Internal(format!( - "partial-origin base changed for {} (stored mtime={}.{}, current mtime={}.{}, stored ctime={}.{}, current ctime={}.{})", - origin.base_path, - origin.base_mtime, - origin.base_mtime_nsec, - stats.mtime, - stats.mtime_nsec, - origin.base_ctime, - origin.base_ctime_nsec, - stats.ctime, - stats.ctime_nsec - ))); - } - Ok(()) - } - - async fn cleanup_partial_origin_if_unlinked(&self, delta_ino: i64) -> Result<()> { - let conn = self.delta.get_connection().await?; - let mut rows = conn - .query("SELECT 1 FROM fs_inode WHERE ino = ?", (delta_ino,)) - .await?; - if rows.next().await?.is_some() { - return Ok(()); - } - - conn.execute("DELETE FROM fs_origin WHERE delta_ino = ?", (delta_ino,)) - .await?; - conn.execute( - "DELETE FROM fs_chunk_override WHERE delta_ino = ?", - (delta_ino,), - ) - .await?; - conn.execute( - "DELETE FROM fs_partial_origin WHERE delta_ino = ?", - (delta_ino,), - ) - .await?; - Ok(()) - } - - /// Promote an overlay inode from base layer to delta layer. - /// - /// When a directory that was originally looked up from base gets a - /// corresponding directory created in delta (via ensure_parent_dirs), - /// we need to update the overlay inode to point to delta. This ensures - /// that operations like readdir and unlink will check the delta layer. - fn promote_to_delta(&self, path: &str, delta_ino: i64) { - let _map_guard = self.map_lock.lock().unwrap(); - let path_map = self.path_map.read().unwrap(); - let overlay_ino = match path_map.get(path) { - Some(&ino) => ino, - None => return, // No existing mapping, nothing to promote - }; - drop(path_map); - - // Update the inode mapping to point to delta - let mut inode_map = self.inode_map.write().unwrap(); - if let Some(info) = inode_map.get_mut(&overlay_ino) { - if info.layer == Layer::Base { - let old_base_ino = info.underlying_ino; - info.layer = Layer::Delta; - info.underlying_ino = delta_ino; - - // Update reverse map: add delta mapping (keep base mapping for origin lookups) - drop(inode_map); - let mut reverse = self.reverse_map.write().unwrap(); - reverse.remove(&(Layer::Base, old_base_ino)); - reverse.insert((Layer::Delta, delta_ino), overlay_ino); - } - } - } - - /// Resolve the delta-layer inode for a parent directory. - /// - /// If the parent's overlay inode already maps to Delta, returns the underlying - /// inode directly. Otherwise, walks the delta filesystem from root using the - /// stored path. Returns Ok(None) if any path component is missing in delta. - async fn resolve_delta_parent(&self, info: &InodeInfo) -> Result> { - if info.layer == Layer::Delta { - return Ok(Some(info.underlying_ino)); - } - let mut ino: i64 = 1; - for comp in info.path.split('/').filter(|s| !s.is_empty()) { - match FileSystem::lookup(&self.delta, ino, comp).await? { - Some(s) if s.is_directory() => ino = s.ino, - Some(_) => return Ok(None), - None => return Ok(None), - } - } - Ok(Some(ino)) - } - - /// Ensure parent directories exist in delta layer - async fn ensure_parent_dirs(&self, path: &str, uid: u32, gid: u32) -> Result<()> { - let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); - - let mut current_path = String::new(); - let mut current_delta_ino: i64 = 1; // Delta root - let mut current_base_ino: i64 = 1; // Base root - - for component in components.iter().take(components.len().saturating_sub(1)) { - current_path = format!("{}/{}", current_path, component); - - // Remove any whiteout for this path - self.remove_whiteout(¤t_path).await?; - - // Check if directory exists in delta - if let Some(stats) = - FileSystem::lookup(&self.delta, current_delta_ino, component).await? - { - if stats.is_directory() { - current_delta_ino = stats.ino; - // Advance base in parallel so it stays in sync - if let Some(bs) = self.base.lookup(current_base_ino, component).await? { - current_base_ino = bs.ino; - } - continue; - } else { - return Err(FsError::NotADirectory.into()); - } - } - - // Not in delta, check base (using the base inode, not delta inode) - let base_stats = self.base.lookup(current_base_ino, component).await?; - let (dir_uid, dir_gid, origin_base_ino) = if let Some(s) = &base_stats { - let base_ino = s.ino; - current_base_ino = base_ino; - (s.uid, s.gid, Some(base_ino)) - } else { - (uid, gid, None) - }; - - // Create directory in delta - let new_stats = FileSystem::mkdir( - &self.delta, - current_delta_ino, - component, - 0o755, - dir_uid, - dir_gid, - ) - .await?; - current_delta_ino = new_stats.ino; - - // Create origin mapping if directory exists in base, so that - // lookups return consistent overlay inodes - if let Some(base_ino) = origin_base_ino { - self.add_origin_mapping(new_stats.ino, base_ino).await?; - // Promote the overlay inode to delta so readdir/unlink will check delta - self.promote_to_delta(¤t_path, new_stats.ino); - } - } - - Ok(()) - } - - /// Copy a file from base to delta for modification - async fn copy_up(&self, path: &str, base_ino: i64) -> Result { - // Parse path to get parent and name - let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); - if components.is_empty() { - return Err(FsError::RootOperation.into()); - } - let name = components.last().unwrap(); - - // Check if already copied up - walk delta to find parent and check for file - let mut parent_ino: i64 = 1; - let mut found_parent = true; - for comp in components.iter().take(components.len() - 1) { - if let Some(stats) = FileSystem::lookup(&self.delta, parent_ino, comp).await? { - parent_ino = stats.ino; - } else { - found_parent = false; - break; - } - } - - // If parent exists in delta, check if file already exists there - if found_parent { - if let Some(stats) = FileSystem::lookup(&self.delta, parent_ino, name).await? { - // Already copied up, return delta inode - return Ok(stats.ino); - } - } - - // Get base stats - let base_stats = self - .base - .getattr(base_ino) - .await? - .ok_or(FsError::NotFound)?; - - // Ensure parent directories exist - self.ensure_parent_dirs(path, base_stats.uid, base_stats.gid) - .await?; - - // Look up parent in delta by walking the path - let mut parent_ino: i64 = 1; // Start at delta root - for comp in components.iter().take(components.len() - 1) { - let stats = FileSystem::lookup(&self.delta, parent_ino, comp) - .await? - .ok_or(FsError::NotFound)?; - parent_ino = stats.ino; - } - - // Copy based on file type - let delta_ino = if base_stats.is_symlink() { - let target = self - .base - .readlink(base_ino) - .await? - .ok_or(FsError::NotFound)?; - let stats = FileSystem::symlink( - &self.delta, - parent_ino, - name, - &target, - base_stats.uid, - base_stats.gid, - ) - .await?; - stats.ino - } else if base_stats.is_directory() { - let stats = FileSystem::mkdir( - &self.delta, - parent_ino, - name, - base_stats.mode & 0o7777, - base_stats.uid, - base_stats.gid, - ) - .await?; - stats.ino - } else { - // Regular file - read content and create - let base_file = self.base.open(base_ino, libc::O_RDONLY).await?; - let content = base_file.pread(0, base_stats.size as u64).await?; - - let (stats, delta_file) = FileSystem::create_file( - &self.delta, - parent_ino, - name, - base_stats.mode, - base_stats.uid, - base_stats.gid, - ) - .await?; - delta_file.pwrite(0, &content).await?; - stats.ino - }; - - // Store origin mapping - self.add_origin_mapping(delta_ino, base_ino).await?; - - Ok(delta_ino) - } - - /// Copy-up a file and update the inode mapping so subsequent operations - /// go to the delta layer. Returns the delta inode. - async fn copy_up_and_update_mapping(&self, overlay_ino: i64, info: &InodeInfo) -> Result { - let delta_ino = self.copy_up(&info.path, info.underlying_ino).await?; - - // Update the inode mapping to point to delta - let _map_guard = self.map_lock.lock().unwrap(); - { - let mut inode_map = self.inode_map.write().unwrap(); - inode_map.insert( - overlay_ino, - InodeInfo { - layer: Layer::Delta, - underlying_ino: delta_ino, - path: info.path.clone(), - }, - ); - } - { - let mut reverse_map = self.reverse_map.write().unwrap(); - // Keep the base mapping so lookups via origin still return the same overlay inode - // (Layer::Base, base_ino) -> overlay_ino is kept - // Add the delta mapping as well - reverse_map.insert((Layer::Delta, delta_ino), overlay_ino); - } - - Ok(delta_ino) - } - - async fn partial_copy_up_and_update_mapping( - &self, - overlay_ino: i64, - info: &InodeInfo, - ) -> Result { - let components: Vec<&str> = info.path.split('/').filter(|s| !s.is_empty()).collect(); - if components.is_empty() { - return Err(FsError::RootOperation.into()); - } - let name = components.last().unwrap(); - - let base_stats = match self.resolve_base_path(&info.path).await? { - Some(stats) => stats, - None => self - .base - .getattr(info.underlying_ino) - .await? - .ok_or(FsError::NotFound)?, - }; - if !base_stats.is_file() { - return self.copy_up_and_update_mapping(overlay_ino, info).await; - } - - self.ensure_parent_dirs(&info.path, base_stats.uid, base_stats.gid) - .await?; - - let mut parent_ino = ROOT_INO; - for comp in components.iter().take(components.len() - 1) { - let stats = FileSystem::lookup(&self.delta, parent_ino, comp) - .await? - .ok_or(FsError::NotFound)?; - parent_ino = stats.ino; - } - - if let Some(stats) = FileSystem::lookup(&self.delta, parent_ino, name).await? { - self.refresh_overlay_mapping(overlay_ino, Layer::Delta, stats.ino, &info.path); - return Ok(stats.ino); - } - - let (stats, _file) = FileSystem::create_file( - &self.delta, - parent_ino, - name, - base_stats.mode, - base_stats.uid, - base_stats.gid, - ) - .await?; - let delta_ino = stats.ino; - - let conn = self.delta.get_connection().await?; - conn.execute( - "UPDATE fs_inode - SET mode = ?, uid = ?, gid = ?, size = ?, atime = ?, mtime = ?, ctime = ?, - atime_nsec = ?, mtime_nsec = ?, ctime_nsec = ?, data_inline = NULL, storage_kind = ? - WHERE ino = ?", - ( - base_stats.mode as i64, - base_stats.uid as i64, - base_stats.gid as i64, - base_stats.size, - base_stats.atime, - base_stats.mtime, - base_stats.ctime, - base_stats.atime_nsec as i64, - base_stats.mtime_nsec as i64, - base_stats.ctime_nsec as i64, - STORAGE_CHUNKED, - delta_ino, - ), - ) - .await?; - self.delta.invalidate_attr(delta_ino); - - self.add_origin_mapping(delta_ino, info.underlying_ino) - .await?; - self.add_partial_origin_mapping(delta_ino, info.underlying_ino, &info.path, &base_stats) - .await?; - self.refresh_overlay_mapping(overlay_ino, Layer::Delta, delta_ino, &info.path); - - Ok(delta_ino) - } - - async fn partial_file_for_delta( - &self, - overlay_ino: i64, - delta_ino: i64, - flags: i32, - ) -> Result { - if let Some(origin) = self.partial_origin_for_delta(delta_ino).await? { - let base_stats = self - .resolve_base_path(&origin.base_path) - .await? - .ok_or(FsError::NotFound)?; - self.validate_partial_origin(&origin, &base_stats)?; - let base_file = self.base.open(base_stats.ino, libc::O_RDONLY).await?; - - // Tier Two Axis C: HostFS passthrough for unmodified delta files. - // - // A partial-origin delta inode that has zero chunk overrides, zero - // full chunks, no inline override, and a size matching the base is - // byte-identical to the base file. In that case the - // OverlayPartialFile wrapper would do a chunk-merge that always - // hits the "no override; read from base" branch -- the SQLite - // round trip is pure overhead. Returning the HostFS fd directly - // sends pread() straight to the kernel VFS for every read on this - // handle, which is most of the cost on `git status` / `git diff` - // / agent stat-storms over a working tree that was copy-up'd but - // not modified. - // - // Restricted to read-only opens: a write open MUST go through the - // OverlayPartialFile wrapper so writes land as `fs_chunk_override` - // rows in the delta DB and never touch the real base file - // (no-real-write invariant from Tier One). - if !is_write_open(flags) { - crate::profiling::record_base_fast_open_passthrough_attempted(); - if self - .delta_has_no_content_overrides(delta_ino, base_stats.size) - .await? - { - crate::profiling::record_base_fast_open_passthrough_succeeded(); - return Ok(base_file); - } - crate::profiling::record_base_fast_open_passthrough_fallback(); - } - - let file: BoxedFile = Arc::new(OverlayPartialFile { - delta: self.delta.clone(), - base: self.base.clone(), - base_file, - origin, - overlay_ino, - delta_ino, - chunk_size: self.delta.chunk_size(), - }); - if (flags & libc::O_TRUNC) != 0 { - file.truncate(0).await?; - } - Ok(file) - } else { - FileSystem::open(&self.delta, delta_ino, flags).await - } - } - - /// Returns true if the delta inode has no content modifications: no chunk - /// overrides, no full chunks, no inline override, and size matches the - /// base. Such a delta is purely a metadata copy and reads can bypass the - /// `OverlayPartialFile` merge path entirely. - /// - /// This is the cheap "is this file unmodified?" check that Tier Two Axis - /// C uses to decide whether `partial_file_for_delta` can short-circuit to - /// a HostFS fd. - async fn delta_has_no_content_overrides(&self, delta_ino: i64, base_size: i64) -> Result { - let conn = self.delta.get_connection().await?; - - // Any per-chunk override? - let mut rows = conn - .query( - "SELECT 1 FROM fs_chunk_override WHERE delta_ino = ? LIMIT 1", - (delta_ino,), - ) - .await?; - if rows.next().await?.is_some() { - return Ok(false); - } - - // Any full chunk in fs_data? (Should be implied by no overrides for - // partial-origin files, but check defensively in case of a - // partial-origin → fully-overridden transition.) - let mut rows = conn - .query("SELECT 1 FROM fs_data WHERE ino = ? LIMIT 1", (delta_ino,)) - .await?; - if rows.next().await?.is_some() { - return Ok(false); - } - - // Size match + no inline override? - let mut rows = conn - .query( - "SELECT size, data_inline FROM fs_inode WHERE ino = ?", - (delta_ino,), - ) - .await?; - let Some(row) = rows.next().await? else { - return Ok(false); - }; - let delta_size: i64 = row - .get(0) - .map_err(|e| Error::Internal(format!("fs_inode.size read failed: {e}")))?; - if delta_size != base_size { - return Ok(false); - } - let inline_value = row - .get_value(1) - .map_err(|e| Error::Internal(format!("fs_inode.data_inline read failed: {e}")))?; - let inline_empty = match inline_value { - Value::Null => true, - Value::Blob(blob) => blob.is_empty(), - _ => true, - }; - if !inline_empty { - return Ok(false); - } - - Ok(true) - } -} - -#[async_trait] -impl File for OverlayPartialFile { - async fn pread(&self, offset: u64, size: u64) -> Result> { - self.validate_current_origin().await?; - let conn = self.delta.get_connection().await?; - let file_size = self.delta_file_size_with_conn(&conn).await?; - if offset >= file_size || size == 0 { - return Ok(Vec::new()); - } - - let read_len = std::cmp::min(size, file_size - offset) as usize; - let chunk_size = self.chunk_size as u64; - let mut result = Vec::with_capacity(read_len); - - while result.len() < read_len { - let current_offset = offset + result.len() as u64; - let chunk_index = current_offset / chunk_size; - let offset_in_chunk = (current_offset % chunk_size) as usize; - let take = std::cmp::min( - self.chunk_size - offset_in_chunk, - read_len.saturating_sub(result.len()), - ); - - let chunk = self.read_merged_chunk_with_conn(&conn, chunk_index).await?; - result.extend_from_slice(&chunk[offset_in_chunk..offset_in_chunk + take]); - } - - Ok(result) - } - - async fn pwrite(&self, offset: u64, data: &[u8]) -> Result<()> { - if data.is_empty() { - return Ok(()); - } - self.pwrite_ranges(vec![WriteRange { - offset, - data: data.to_vec(), - }]) - .await - } - - async fn pwrite_ranges(&self, ranges: Vec) -> Result<()> { - if ranges.iter().all(|range| range.data.is_empty()) { - return Ok(()); - } - let conn = self.delta.get_connection().await?; - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - - let result: Result<()> = async { - let mut new_size = self.delta_file_size_with_conn(&conn).await?; - for range in ranges { - if range.data.is_empty() { - continue; - } - let write_end = range - .offset - .checked_add(range.data.len() as u64) - .ok_or_else(|| Error::Internal("file write offset overflow".to_string()))?; - new_size = std::cmp::max(new_size, write_end); - let chunk_size = self.chunk_size as u64; - let mut written = 0usize; - - while written < range.data.len() { - let current_offset = range.offset + written as u64; - let chunk_index = current_offset / chunk_size; - let offset_in_chunk = (current_offset % chunk_size) as usize; - let remaining_in_chunk = self.chunk_size - offset_in_chunk; - let to_write = std::cmp::min(remaining_in_chunk, range.data.len() - written); - - let mut chunk = self - .read_merged_chunk_with_conn(&conn, chunk_index) - .await?; - chunk[offset_in_chunk..offset_in_chunk + to_write] - .copy_from_slice(&range.data[written..written + to_write]); - - conn.execute( - "INSERT OR REPLACE INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?)", - ( - self.delta_ino, - chunk_index as i64, - Value::Blob(chunk), - ), - ) - .await?; - conn.execute( - "INSERT OR IGNORE INTO fs_chunk_override (delta_ino, chunk_index) VALUES (?, ?)", - (self.delta_ino, chunk_index as i64), - ) - .await?; - - written += to_write; - } - } - - let (now_secs, now_nsec) = current_timestamp()?; - conn.execute( - "UPDATE fs_inode - SET size = ?, data_inline = NULL, storage_kind = ?, mtime = ?, ctime = ?, - mtime_nsec = ?, ctime_nsec = ? - WHERE ino = ?", - ( - new_size as i64, - STORAGE_CHUNKED, - now_secs, - now_secs, - now_nsec, - now_nsec, - self.delta_ino, - ), - ) - .await?; - Ok(()) - } - .await; - - match result { - Ok(()) => { - txn.commit().await?; - self.delta.invalidate_attr(self.delta_ino); - Ok(()) - } - Err(e) => { - let _ = txn.rollback().await; - Err(e) - } - } - } - - async fn truncate(&self, size: u64) -> Result<()> { - let conn = self.delta.get_connection().await?; - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - - let result: Result<()> = async { - let current_size = self.delta_file_size_with_conn(&conn).await?; - let chunk_size = self.chunk_size as u64; - - if size == 0 { - conn.execute("DELETE FROM fs_data WHERE ino = ?", (self.delta_ino,)) - .await?; - conn.execute( - "DELETE FROM fs_chunk_override WHERE delta_ino = ?", - (self.delta_ino,), - ) - .await?; - } else if size < current_size { - let last_chunk = (size - 1) / chunk_size; - conn.execute( - "DELETE FROM fs_data WHERE ino = ? AND chunk_index > ?", - (self.delta_ino, last_chunk as i64), - ) - .await?; - conn.execute( - "DELETE FROM fs_chunk_override WHERE delta_ino = ? AND chunk_index > ?", - (self.delta_ino, last_chunk as i64), - ) - .await?; - - let end_in_last_chunk = ((size - 1) % chunk_size + 1) as usize; - if self.chunk_is_override_with_conn(&conn, last_chunk).await? { - let mut chunk = self - .delta_chunk_with_conn(&conn, last_chunk) - .await? - .unwrap_or_default(); - if chunk.len() > end_in_last_chunk { - chunk.truncate(end_in_last_chunk); - conn.execute( - "UPDATE fs_data SET data = ? WHERE ino = ? AND chunk_index = ?", - (Value::Blob(chunk), self.delta_ino, last_chunk as i64), - ) - .await?; - } - } - } - - let origin_base_size = self.partial_base_size_with_conn(&conn).await?; - if size < origin_base_size { - conn.execute( - "UPDATE fs_partial_origin SET base_size = ? WHERE delta_ino = ?", - (size as i64, self.delta_ino), - ) - .await?; - } - - let (now_secs, now_nsec) = current_timestamp()?; - conn.execute( - "UPDATE fs_inode - SET size = ?, data_inline = NULL, storage_kind = ?, mtime = ?, ctime = ?, - mtime_nsec = ?, ctime_nsec = ? - WHERE ino = ?", - ( - size as i64, - STORAGE_CHUNKED, - now_secs, - now_secs, - now_nsec, - now_nsec, - self.delta_ino, - ), - ) - .await?; - Ok(()) - } - .await; - - match result { - Ok(()) => { - txn.commit().await?; - self.delta.invalidate_attr(self.delta_ino); - Ok(()) - } - Err(e) => { - let _ = txn.rollback().await; - Err(e) - } - } - } - - async fn fsync(&self) -> Result<()> { - self.delta.fsync("/").await - } - - async fn fstat(&self) -> Result { - let mut stats = FileSystem::getattr(&self.delta, self.delta_ino) - .await? - .ok_or(FsError::NotFound)?; - stats.ino = self.overlay_ino; - Ok(stats) - } -} - -impl OverlayPartialFile { - async fn resolve_origin_base_stats(&self) -> Result> { - let mut ino = ROOT_INO; - if self.origin.base_path == "/" { - return self.base.getattr(ino).await; - } - - let mut stats = None; - for component in self.origin.base_path.split('/').filter(|s| !s.is_empty()) { - let Some(next) = self.base.lookup(ino, component).await? else { - return Ok(None); - }; - ino = next.ino; - stats = Some(next); - } - Ok(stats) - } - - async fn validate_current_origin(&self) -> Result<()> { - let stats = self - .resolve_origin_base_stats() - .await? - .ok_or(FsError::NotFound)?; - if stats.size != self.origin.base_fingerprint_size - || stats.mtime != self.origin.base_mtime - || stats.mtime_nsec != self.origin.base_mtime_nsec - || stats.ctime != self.origin.base_ctime - || stats.ctime_nsec != self.origin.base_ctime_nsec - { - return Err(Error::Internal(format!( - "partial-origin base changed for {}", - self.origin.base_path - ))); - } - Ok(()) - } - - async fn delta_file_size_with_conn(&self, conn: &Connection) -> Result { - let mut rows = conn - .query("SELECT size FROM fs_inode WHERE ino = ?", (self.delta_ino,)) - .await?; - if let Some(row) = rows.next().await? { - Ok(row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u64) - } else { - Err(FsError::NotFound.into()) - } - } - - async fn partial_base_size_with_conn(&self, conn: &Connection) -> Result { - let mut rows = conn - .query( - "SELECT base_size FROM fs_partial_origin WHERE delta_ino = ?", - (self.delta_ino,), - ) - .await?; - if let Some(row) = rows.next().await? { - Ok(row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u64) - } else { - Err(FsError::NotFound.into()) - } - } - - async fn chunk_is_override_with_conn( - &self, - conn: &Connection, - chunk_index: u64, - ) -> Result { - let mut rows = conn - .query( - "SELECT 1 FROM fs_chunk_override WHERE delta_ino = ? AND chunk_index = ?", - (self.delta_ino, chunk_index as i64), - ) - .await?; - Ok(rows.next().await?.is_some()) - } - - async fn delta_chunk_with_conn( - &self, - conn: &Connection, - chunk_index: u64, - ) -> Result>> { - let mut rows = conn - .query( - "SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?", - (self.delta_ino, chunk_index as i64), - ) - .await?; - if let Some(row) = rows.next().await? { - match row.get_value(0) { - Ok(Value::Blob(data)) => Ok(Some(data)), - _ => Ok(Some(Vec::new())), - } - } else { - Ok(None) - } - } - - async fn read_merged_chunk_with_conn( - &self, - conn: &Connection, - chunk_index: u64, - ) -> Result> { - if self.chunk_is_override_with_conn(conn, chunk_index).await? { - let mut chunk = self - .delta_chunk_with_conn(conn, chunk_index) - .await? - .unwrap_or_default(); - chunk.resize(self.chunk_size, 0); - return Ok(chunk); - } - - let base_size = self.partial_base_size_with_conn(conn).await?; - let chunk_start = chunk_index - .checked_mul(self.chunk_size as u64) - .ok_or_else(|| Error::Internal("chunk offset overflow".to_string()))?; - let mut chunk = if chunk_start < base_size { - self.validate_current_origin().await?; - let readable = std::cmp::min(self.chunk_size as u64, base_size - chunk_start); - self.base_file.pread(chunk_start, readable).await? - } else { - Vec::new() - }; - chunk.resize(self.chunk_size, 0); - Ok(chunk) - } -} - -#[async_trait] -impl FileSystem for OverlayFS { - async fn lookup(&self, parent_ino: i64, name: &str) -> Result> { - crate::profiling::record_lookup(); - trace!( - "OverlayFS::lookup: parent_ino={}, name={}", - parent_ino, - name - ); - - let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; - let path = self.build_path(parent_ino, name)?; - - // Check for whiteout - if self.is_whiteout(&path) { - crate::profiling::record_lookup_whiteout(); - crate::profiling::record_negative_lookup(); - return Ok(None); - } - - // Try delta first - let delta_parent_ino = self.resolve_delta_parent(&parent_info).await?; - - // Look up in delta (only if we resolved the correct parent) - if let Some(delta_stats) = match delta_parent_ino { - Some(ino) => { - crate::profiling::record_lookup_delta(); - self.delta.lookup(ino, name).await? - } - None => None, - } { - let delta_ino = delta_stats.ino; - let ino = self.get_or_create_overlay_ino(Layer::Delta, delta_ino, &path); - let mut stats = delta_stats; - - // Origin mapping: reuse an existing Base overlay inode for stable - // numbering within a session. After remount the base_ino stored in - // the mapping may be stale (the new HostFS has a fresh inode cache), - // so only use it when the reverse_map already contains a live entry. - // Otherwise keep the Delta overlay inode — the downstream code - // already walks base from root when the parent is tagged Delta. - if let Some(base_ino) = self.get_origin_ino(stats.ino) { - if let Some(existing_ino) = self.live_origin_overlay_ino(base_ino, &path) { - self.refresh_overlay_mapping(existing_ino, Layer::Delta, delta_ino, &path); - stats.ino = existing_ino; - } else { - stats.ino = ino; - } - } else { - stats.ino = ino; - } - - return Ok(Some(stats)); - } - - // Try base - let base_parent_ino = if parent_info.layer == Layer::Base { - parent_info.underlying_ino - } else { - // Need to find corresponding base parent by path - // For root, use base root (1) - if parent_info.path == "/" { - 1 - } else { - // Walk the base to find the parent - let mut base_ino: i64 = 1; - let components: Vec<_> = parent_info - .path - .split('/') - .filter(|s| !s.is_empty()) - .collect(); - crate::profiling::record_path_resolution(components.len() as u64); - for comp in components { - if let Some(s) = self.base.lookup(base_ino, comp).await? { - base_ino = s.ino; - } else { - crate::profiling::record_negative_lookup(); - return Ok(None); - } - } - base_ino - } - }; - - crate::profiling::record_lookup_base(); - if let Some(base_stats) = self.base.lookup(base_parent_ino, name).await? { - let ino = self.get_or_create_overlay_ino(Layer::Base, base_stats.ino, &path); - let mut stats = base_stats; - stats.ino = ino; - return Ok(Some(stats)); - } - - crate::profiling::record_negative_lookup(); - Ok(None) - } - - async fn getattr(&self, ino: i64) -> Result> { - crate::profiling::record_getattr(); - crate::profiling::record_attr_cache_miss(); - trace!("OverlayFS::getattr: ino={}", ino); - - let info = match self.get_inode_info(ino) { - Some(i) => i, - None => return Ok(None), - }; - if info.layer == Layer::Base && self.is_whiteout(&info.path) { - crate::profiling::record_lookup_whiteout(); - return Ok(None); - } - - let stats = match info.layer { - Layer::Delta => FileSystem::getattr(&self.delta, info.underlying_ino).await?, - Layer::Base => self.base.getattr(info.underlying_ino).await?, - }; - - Ok(stats.map(|mut s| { - s.ino = ino; - s - })) - } - - async fn readlink(&self, ino: i64) -> Result> { - trace!("OverlayFS::readlink: ino={}", ino); - - let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; - if info.layer == Layer::Base && self.is_whiteout(&info.path) { - return Ok(None); - } - - match info.layer { - Layer::Delta => FileSystem::readlink(&self.delta, info.underlying_ino).await, - Layer::Base => self.base.readlink(info.underlying_ino).await, - } - } - - async fn readdir(&self, ino: i64) -> Result>> { - crate::profiling::record_readdir(); - trace!("OverlayFS::readdir: ino={}", ino); - - let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; - let child_whiteouts = self.get_child_whiteouts(&info.path); - - let mut entries = HashSet::new(); - - // Get delta entries - if info.layer == Layer::Delta { - if let Some(delta_entries) = self.delta.readdir(info.underlying_ino).await? { - for entry in delta_entries { - let entry_path = if info.path == "/" { - format!("/{}", entry) - } else { - format!("{}/{}", info.path, entry) - }; - if !self.is_whiteout(&entry_path) && !child_whiteouts.contains(&entry) { - entries.insert(entry); - } - } - } - } - - // Get base entries (need to resolve base inode from path) - let base_ino = if info.layer == Layer::Base { - Some(info.underlying_ino) - } else { - // Walk base to find corresponding directory - let components: Vec<&str> = info.path.split('/').filter(|s| !s.is_empty()).collect(); - let mut ino: i64 = 1; - let mut found_all = true; - crate::profiling::record_path_resolution(components.len() as u64); - for comp in &components { - if let Some(s) = self.base.lookup(ino, comp).await? { - ino = s.ino; - } else { - found_all = false; - break; - } - } - if found_all { - Some(ino) - } else { - None - } - }; - - if let Some(base_ino) = base_ino { - if let Some(base_entries) = self.base.readdir(base_ino).await? { - for entry in base_entries { - let entry_path = if info.path == "/" { - format!("/{}", entry) - } else { - format!("{}/{}", info.path, entry) - }; - if !self.is_whiteout(&entry_path) && !child_whiteouts.contains(&entry) { - entries.insert(entry); - } - } - } - } - - let mut result: Vec<_> = entries.into_iter().collect(); - result.sort(); - Ok(Some(result)) - } - - async fn readdir_plus(&self, ino: i64) -> Result>> { - crate::profiling::record_readdir_plus(); - trace!("OverlayFS::readdir_plus: ino={}", ino); - - let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; - let child_whiteouts = self.get_child_whiteouts(&info.path); - - let mut entries_map: HashMap = HashMap::new(); - - // Get base entries first (so delta can override) - let base_ino = if info.layer == Layer::Base { - Some(info.underlying_ino) - } else { - let components: Vec<&str> = info.path.split('/').filter(|s| !s.is_empty()).collect(); - let mut ino: i64 = 1; - let mut found_all = true; - crate::profiling::record_path_resolution(components.len() as u64); - for comp in &components { - if let Some(s) = self.base.lookup(ino, comp).await? { - ino = s.ino; - } else { - found_all = false; - break; - } - } - if found_all { - Some(ino) - } else { - None - } - }; - - if let Some(base_ino) = base_ino { - if let Some(base_entries) = self.base.readdir_plus(base_ino).await? { - for mut entry in base_entries { - let entry_path = if info.path == "/" { - format!("/{}", entry.name) - } else { - format!("{}/{}", info.path, entry.name) - }; - - if !self.is_whiteout(&entry_path) && !child_whiteouts.contains(&entry.name) { - let overlay_ino = self.get_or_create_overlay_ino( - Layer::Base, - entry.stats.ino, - &entry_path, - ); - entry.stats.ino = overlay_ino; - entries_map.insert(entry.name.clone(), entry); - } - } - } - } - - // Get delta entries (override base) - if info.layer == Layer::Delta { - if let Some(delta_entries) = self.delta.readdir_plus(info.underlying_ino).await? { - for mut entry in delta_entries { - let entry_path = if info.path == "/" { - format!("/{}", entry.name) - } else { - format!("{}/{}", info.path, entry.name) - }; - if self.is_whiteout(&entry_path) || child_whiteouts.contains(&entry.name) { - continue; - } - - // Check for origin mapping - let delta_ino = entry.stats.ino; - if let Some(base_ino) = self.get_origin_ino(entry.stats.ino) { - let overlay_ino = - self.get_or_create_overlay_ino(Layer::Delta, delta_ino, &entry_path); - if let Some(existing_ino) = - self.live_origin_overlay_ino(base_ino, &entry_path) - { - self.refresh_overlay_mapping( - existing_ino, - Layer::Delta, - delta_ino, - &entry_path, - ); - entry.stats.ino = existing_ino; - } else { - entry.stats.ino = overlay_ino; - } - } else { - let overlay_ino = self.get_or_create_overlay_ino( - Layer::Delta, - entry.stats.ino, - &entry_path, - ); - entry.stats.ino = overlay_ino; - } - - entries_map.insert(entry.name.clone(), entry); - } - } - } - - let mut result: Vec<_> = entries_map.into_values().collect(); - result.sort_by(|a, b| a.name.cmp(&b.name)); - Ok(Some(result)) - } - - async fn chmod(&self, ino: i64, mode: u32) -> Result<()> { - trace!("OverlayFS::chmod: ino={}, mode={:o}", ino, mode); - - let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; - if info.layer == Layer::Base && self.is_whiteout(&info.path) { - return Err(FsError::NotFound.into()); - } - - let delta_ino = match info.layer { - Layer::Delta => info.underlying_ino, - Layer::Base => { - let base_stats = self - .base - .getattr(info.underlying_ino) - .await? - .ok_or(FsError::NotFound)?; - if self.partial_origin_policy.permits(&base_stats) { - self.partial_copy_up_and_update_mapping(ino, &info).await? - } else { - self.copy_up_and_update_mapping(ino, &info).await? - } - } - }; - - self.delta.chmod(delta_ino, mode).await - } - - async fn chown(&self, ino: i64, uid: Option, gid: Option) -> Result<()> { - trace!( - "OverlayFS::chown: ino={}, uid={:?}, gid={:?}", - ino, - uid, - gid - ); - - let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; - if info.layer == Layer::Base && self.is_whiteout(&info.path) { - return Err(FsError::NotFound.into()); - } - - let delta_ino = match info.layer { - Layer::Delta => info.underlying_ino, - Layer::Base => { - let base_stats = self - .base - .getattr(info.underlying_ino) - .await? - .ok_or(FsError::NotFound)?; - if self.partial_origin_policy.permits(&base_stats) { - self.partial_copy_up_and_update_mapping(ino, &info).await? - } else { - self.copy_up_and_update_mapping(ino, &info).await? - } - } - }; - - self.delta.chown(delta_ino, uid, gid).await - } - - async fn utimens(&self, ino: i64, atime: TimeChange, mtime: TimeChange) -> Result<()> { - trace!("OverlayFS::utimens: ino={}", ino); - - let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; - if info.layer == Layer::Base && self.is_whiteout(&info.path) { - return Err(FsError::NotFound.into()); - } - - let delta_ino = match info.layer { - Layer::Delta => info.underlying_ino, - Layer::Base => { - let base_stats = self - .base - .getattr(info.underlying_ino) - .await? - .ok_or(FsError::NotFound)?; - if self.partial_origin_policy.permits(&base_stats) { - self.partial_copy_up_and_update_mapping(ino, &info).await? - } else { - self.copy_up_and_update_mapping(ino, &info).await? - } - } - }; - - self.delta.utimens(delta_ino, atime, mtime).await - } - - async fn keep_cache_for_read_open(&self, ino: i64, flags: i32) -> Result> { - if is_write_open(flags) { - return Ok(None); - } - - let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; - match info.layer { - Layer::Base => { - if self.is_whiteout(&info.path) { - return Ok(None); - } - let Some(stats) = self.base.getattr(info.underlying_ino).await? else { - return Ok(None); - }; - Ok(stats.is_file().then_some(stats)) - } - // Delta (DB-backed) files inherit the AgentFS keep-cache policy: - // the adapter fingerprint guard revalidates per open. - Layer::Delta => { - FileSystem::keep_cache_for_read_open(&self.delta, info.underlying_ino, flags).await - } - } - } - - async fn open(&self, ino: i64, flags: i32) -> Result { - trace!("OverlayFS::open: ino={}", ino); - - let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; - if info.layer == Layer::Base && self.is_whiteout(&info.path) { - return Err(FsError::NotFound.into()); - } - - match info.layer { - Layer::Delta => { - return self - .partial_file_for_delta(ino, info.underlying_ino, flags) - .await; - } - Layer::Base if !is_write_open(flags) => { - return self.base.open(info.underlying_ino, flags).await; - } - Layer::Base => { - let base_stats = self - .base - .getattr(info.underlying_ino) - .await? - .ok_or(FsError::NotFound)?; - if self.partial_origin_policy.permits(&base_stats) { - let delta_ino = self.partial_copy_up_and_update_mapping(ino, &info).await?; - return self.partial_file_for_delta(ino, delta_ino, flags).await; - } - } - } - - let delta_ino = self.copy_up_and_update_mapping(ino, &info).await?; - - FileSystem::open(&self.delta, delta_ino, flags).await - } - - async fn mkdir( - &self, - parent_ino: i64, - name: &str, - mode: u32, - uid: u32, - gid: u32, - ) -> Result { - trace!("OverlayFS::mkdir: parent_ino={}, name={}", parent_ino, name); - - let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; - let path = self.build_path(parent_ino, name)?; - - // Check if already exists - if self.lookup(parent_ino, name).await?.is_some() { - return Err(FsError::AlreadyExists.into()); - } - - // Remove whiteout if exists - self.remove_whiteout(&path).await?; - - // Ensure parent dirs exist in delta - self.ensure_parent_dirs(&path, uid, gid).await?; - - let delta_parent_ino = self - .resolve_delta_parent(&parent_info) - .await? - .ok_or(FsError::NotFound)?; - - let mut stats = - FileSystem::mkdir(&self.delta, delta_parent_ino, name, mode, uid, gid).await?; - let overlay_ino = self.get_or_create_overlay_ino(Layer::Delta, stats.ino, &path); - stats.ino = overlay_ino; - - Ok(stats) - } - - async fn create_file( - &self, - parent_ino: i64, - name: &str, - mode: u32, - uid: u32, - gid: u32, - ) -> Result<(Stats, BoxedFile)> { - trace!( - "OverlayFS::create_file: parent_ino={}, name={}", - parent_ino, - name - ); - - let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; - let path = self.build_path(parent_ino, name)?; - - // Remove whiteout if exists - self.remove_whiteout(&path).await?; - - // Ensure parent dirs exist in delta - self.ensure_parent_dirs(&path, uid, gid).await?; - - let delta_parent_ino = self - .resolve_delta_parent(&parent_info) - .await? - .ok_or(FsError::NotFound)?; - - let (mut stats, file) = - FileSystem::create_file(&self.delta, delta_parent_ino, name, mode, uid, gid).await?; - let overlay_ino = self.get_or_create_overlay_ino(Layer::Delta, stats.ino, &path); - stats.ino = overlay_ino; - - Ok((stats, file)) - } - - async fn mknod( - &self, - parent_ino: i64, - name: &str, - mode: u32, - rdev: u64, - uid: u32, - gid: u32, - ) -> Result { - trace!("OverlayFS::mknod: parent_ino={}, name={}", parent_ino, name); - - let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; - let path = self.build_path(parent_ino, name)?; - - self.remove_whiteout(&path).await?; - self.ensure_parent_dirs(&path, uid, gid).await?; - - let delta_parent_ino = self - .resolve_delta_parent(&parent_info) - .await? - .ok_or(FsError::NotFound)?; - - let mut stats = - FileSystem::mknod(&self.delta, delta_parent_ino, name, mode, rdev, uid, gid).await?; - let overlay_ino = self.get_or_create_overlay_ino(Layer::Delta, stats.ino, &path); - stats.ino = overlay_ino; - - Ok(stats) - } - - async fn symlink( - &self, - parent_ino: i64, - name: &str, - target: &str, - uid: u32, - gid: u32, - ) -> Result { - trace!( - "OverlayFS::symlink: parent_ino={}, name={}, target={}", - parent_ino, - name, - target - ); - - let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; - let path = self.build_path(parent_ino, name)?; - - self.remove_whiteout(&path).await?; - self.ensure_parent_dirs(&path, uid, gid).await?; - - let delta_parent_ino = self - .resolve_delta_parent(&parent_info) - .await? - .ok_or(FsError::NotFound)?; - - let mut stats = - FileSystem::symlink(&self.delta, delta_parent_ino, name, target, uid, gid).await?; - let overlay_ino = self.get_or_create_overlay_ino(Layer::Delta, stats.ino, &path); - stats.ino = overlay_ino; - - Ok(stats) - } - - async fn unlink(&self, parent_ino: i64, name: &str) -> Result<()> { - trace!( - "OverlayFS::unlink: parent_ino={}, name={}", - parent_ino, - name - ); - - let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; - let path = self.build_path(parent_ino, name)?; - - // Check if it exists - let stats = self - .lookup(parent_ino, name) - .await? - .ok_or(FsError::NotFound)?; - if stats.is_directory() { - return Err(FsError::IsADirectory.into()); - } - - // Try to remove from delta. Walk the delta layer to find the parent, - // since the overlay parent may map to Base even when a copy-up exists in delta. - if let Some(dpi) = self.resolve_delta_parent(&parent_info).await? { - let removed_delta_ino = FileSystem::lookup(&self.delta, dpi, name) - .await? - .map(|stats| stats.ino); - match FileSystem::unlink(&self.delta, dpi, name).await { - Ok(()) => {} - Err(crate::error::Error::Fs(FsError::NotFound)) => {} - Err(e) => return Err(e), - } - if let Some(delta_ino) = removed_delta_ino { - self.cleanup_partial_origin_if_unlinked(delta_ino).await?; - } - } - - // If the file is still visible through the overlay after delta removal, - // it must be coming from the base layer — create a whiteout to hide it. - if self.lookup(parent_ino, name).await?.is_some() { - self.create_whiteout(&path).await?; - } - - Ok(()) - } - - async fn rmdir(&self, parent_ino: i64, name: &str) -> Result<()> { - trace!("OverlayFS::rmdir: parent_ino={}, name={}", parent_ino, name); - - let parent_info = self.get_inode_info(parent_ino).ok_or(FsError::NotFound)?; - let path = self.build_path(parent_ino, name)?; - - // Check if it exists and is a directory - let stats = self - .lookup(parent_ino, name) - .await? - .ok_or(FsError::NotFound)?; - if !stats.is_directory() { - return Err(FsError::NotADirectory.into()); - } - - // Check if directory is empty (in overlay view) - let dir_entries = self.readdir(stats.ino).await?.unwrap_or_default(); - if !dir_entries.is_empty() { - return Err(FsError::NotEmpty.into()); - } - - // Try to remove from delta. Walk the delta layer to find the parent, - // since the overlay parent may map to Base even when a copy-up exists in delta. - if let Some(dpi) = self.resolve_delta_parent(&parent_info).await? { - match FileSystem::rmdir(&self.delta, dpi, name).await { - Ok(()) => {} - Err(crate::error::Error::Fs(FsError::NotFound)) => {} - Err(e) => return Err(e), - } - } - - // If the directory is still visible through the overlay after delta removal, - // it must be coming from the base layer — create a whiteout to hide it. - if self.lookup(parent_ino, name).await?.is_some() { - self.create_whiteout(&path).await?; - } - - Ok(()) - } - - async fn link(&self, ino: i64, newparent_ino: i64, newname: &str) -> Result { - trace!( - "OverlayFS::link: ino={}, newparent_ino={}, newname={}", - ino, - newparent_ino, - newname - ); - - let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; - let parent_info = self - .get_inode_info(newparent_ino) - .ok_or(FsError::NotFound)?; - let new_path = self.build_path(newparent_ino, newname)?; - - // Ensure file is in delta (copy up if needed) - let delta_ino = if info.layer == Layer::Delta { - info.underlying_ino - } else { - self.copy_up(&info.path, info.underlying_ino).await? - }; - - self.remove_whiteout(&new_path).await?; - self.ensure_parent_dirs(&new_path, 0, 0).await?; - - // Resolve delta parent AFTER ensure_parent_dirs so the directories exist. - let delta_parent_ino = self - .resolve_delta_parent(&parent_info) - .await? - .ok_or(FsError::NotFound)?; - - let mut stats = FileSystem::link(&self.delta, delta_ino, delta_parent_ino, newname).await?; - stats.ino = ino; // Keep original overlay inode - - Ok(stats) - } - - async fn rename( - &self, - oldparent_ino: i64, - oldname: &str, - newparent_ino: i64, - newname: &str, - ) -> Result<()> { - trace!( - "OverlayFS::rename: oldparent={}, oldname={}, newparent={}, newname={}", - oldparent_ino, - oldname, - newparent_ino, - newname - ); - - let old_parent_info = self - .get_inode_info(oldparent_ino) - .ok_or(FsError::NotFound)?; - let new_parent_info = self - .get_inode_info(newparent_ino) - .ok_or(FsError::NotFound)?; - let old_path = self.build_path(oldparent_ino, oldname)?; - let new_path = self.build_path(newparent_ino, newname)?; - - // Get source stats - let src_stats = self - .lookup(oldparent_ino, oldname) - .await? - .ok_or(FsError::NotFound)?; - let src_info = self - .get_inode_info(src_stats.ino) - .ok_or(FsError::NotFound)?; - - // Ensure source is in delta first. - let delta_src_ino = if src_info.layer == Layer::Base { - self.copy_up(&old_path, src_info.underlying_ino).await? - } else { - src_info.underlying_ino - }; - - // Remove whiteout at destination - self.remove_whiteout(&new_path).await?; - self.ensure_parent_dirs(&new_path, 0, 0).await?; - - // Resolve delta parents AFTER copy_up / ensure_parent_dirs, - // since those create the parent directories in delta. - let delta_src_parent_ino = self - .resolve_delta_parent(&old_parent_info) - .await? - .ok_or(FsError::NotFound)?; - let delta_dst_parent_ino = self - .resolve_delta_parent(&new_parent_info) - .await? - .ok_or(FsError::NotFound)?; - - // Perform rename in delta - FileSystem::rename( - &self.delta, - delta_src_parent_ino, - oldname, - delta_dst_parent_ino, - newname, - ) - .await?; - self.refresh_overlay_mapping(src_stats.ino, Layer::Delta, delta_src_ino, &new_path); - - // If the old file is still visible through the overlay after the rename, - // it must be coming from the base layer — create a whiteout to hide it. - if self.lookup(oldparent_ino, oldname).await?.is_some() { - self.create_whiteout(&old_path).await?; - } - - Ok(()) - } - - async fn statfs(&self) -> Result { - FileSystem::statfs(&self.delta).await - } - - async fn drain_inode_writes(&self, ino: i64) -> Result<()> { - let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; - match info.layer { - Layer::Delta => FileSystem::drain_inode_writes(&self.delta, info.underlying_ino).await, - Layer::Base => self.base.drain_inode_writes(info.underlying_ino).await, - } - } - - async fn drain_all(&self) -> Result<()> { - FileSystem::drain_all(&self.delta).await?; - self.base.drain_all().await?; - Ok(()) - } - - async fn finalize(&self) -> Result<()> { - FileSystem::finalize(&self.delta).await?; - self.base.finalize().await?; - Ok(()) - } - - async fn retain_lookup(&self, ino: i64, nlookup: u64) -> Result<()> { - let info = self.get_inode_info(ino).ok_or(FsError::NotFound)?; - match info.layer { - Layer::Delta => { - FileSystem::retain_lookup(&self.delta, info.underlying_ino, nlookup).await - } - Layer::Base => self.base.retain_lookup(info.underlying_ino, nlookup).await, - } - } - - async fn forget(&self, ino: i64, nlookup: u64) { - // Look up the inode info to determine which layer it belongs to - let info = match self.get_inode_info(ino) { - Some(i) => i, - None => return, // Unknown inode, nothing to forget - }; - - // Pass through to the appropriate layer - match info.layer { - Layer::Delta => { - // Delta (AgentFS) doesn't cache fds, but call it anyway for completeness - FileSystem::forget(&self.delta, info.underlying_ino, nlookup).await; - } - Layer::Base => { - // Base layer (HostFS) caches O_PATH fds and needs forget - self.base.forget(info.underlying_ino, nlookup).await; - } - } - - // Note: We don't remove from inode_map here because the overlay layer's - // inode mapping is relatively lightweight (no fd). The base layer's - // forget handles the actual fd cleanup. - } -} - -#[cfg(all(test, any(target_os = "linux", target_os = "macos")))] -mod tests { use super::*; - use crate::filesystem::HostFS; + use crate::fs::HostFS; + use crate::fs::{FsError, TimeChange}; use crate::DEFAULT_FILE_MODE; - use std::os::unix::fs::PermissionsExt; + use std::os::unix::fs::{MetadataExt, PermissionsExt}; use tempfile::tempdir; async fn create_test_overlay() -> Result<(OverlayFS, tempfile::TempDir, tempfile::TempDir)> { @@ -2618,7 +119,22 @@ mod tests { } #[tokio::test] - async fn test_overlay_keep_cache_only_for_read_only_base_files() -> Result<()> { + async fn base_fast_path_fstat_reports_overlay_identity() -> Result<()> { + let (overlay, base_dir, _delta_dir) = create_test_overlay().await?; + + let stats = overlay.lookup(ROOT_INO, "base.txt").await?.unwrap(); + let host_ino = std::fs::metadata(base_dir.path().join("base.txt"))?.ino() as i64; + let file = overlay.open(stats.ino, libc::O_RDONLY).await?; + + let fstat = file.fstat().await?; + assert_eq!(fstat.ino, stats.ino); + assert_ne!(fstat.ino, host_ino); + + Ok(()) + } + + #[tokio::test] + async fn test_overlay_keep_cache_rejects_base_files() -> Result<()> { let (overlay, _base_dir, _delta_dir) = create_test_overlay().await?; let stats = overlay.lookup(ROOT_INO, "base.txt").await?.unwrap(); @@ -2626,13 +142,8 @@ mod tests { .keep_cache_for_read_open(stats.ino, libc::O_RDONLY) .await?; assert!( - granted.is_some(), - "read-only base files are eligible for FOPEN_KEEP_CACHE" - ); - assert_eq!( - granted.map(|s| s.size), - Some(stats.size), - "keep-cache grant must carry the stats it was decided on" + granted.is_none(), + "base files can drift outside the mount and must not get FOPEN_KEEP_CACHE" ); assert!( overlay @@ -2769,6 +280,66 @@ mod tests { Ok(()) } + #[tokio::test] + async fn overlay_partial_origin_store_characterization() -> Result<()> { + let base_dir = tempdir()?; + let delta_dir = tempdir()?; + let db_path = delta_dir.path().join("delta.db"); + let delta = AgentFS::new(db_path.to_str().unwrap()).await?; + let chunk_size = delta.chunk_size(); + let mut expected = patterned_bytes(chunk_size * 2 + 17, 0x2a); + std::fs::write(base_dir.path().join("large.bin"), &expected)?; + + let base = Arc::new(HostFS::new(base_dir.path())?); + let overlay = OverlayFS::new_with_partial_origin(base, delta, true); + overlay.init(base_dir.path().to_str().unwrap()).await?; + + let stats = overlay.lookup(ROOT_INO, "large.bin").await?.unwrap(); + let file = overlay.open(stats.ino, libc::O_RDWR).await?; + + let cross_chunk_offset = chunk_size as u64 - 2; + file.pwrite(cross_chunk_offset, b"STORE").await?; + expected[cross_chunk_offset as usize..cross_chunk_offset as usize + 5] + .copy_from_slice(b"STORE"); + + assert_eq!( + scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_data").await?, + 2, + "shared store write should materialize only the touched chunks" + ); + assert_eq!( + scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_chunk_override").await?, + 2, + "shared store hook should mark exactly the touched chunks as overrides" + ); + assert_eq!( + file.pread(chunk_size as u64 - 4, 10).await?, + expected[chunk_size - 4..chunk_size + 6], + "partial-origin reads should merge store-owned chunks with base fallback bytes" + ); + + file.truncate(chunk_size as u64 + 1).await?; + expected.truncate(chunk_size + 1); + assert_eq!(file.fstat().await?.size, (chunk_size + 1) as i64); + assert_eq!( + scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_chunk_override").await?, + 2, + "truncate within an overridden chunk should keep in-range override markers" + ); + assert_eq!( + file.pread(chunk_size as u64 - 4, 8).await?, + expected[chunk_size - 4..], + "truncate through the shared store should preserve in-range override bytes" + ); + assert_eq!( + std::fs::read(base_dir.path().join("large.bin"))?, + patterned_bytes(chunk_size * 2 + 17, 0x2a), + "partial-origin store writes and truncates must not mutate the base file" + ); + + Ok(()) + } + #[tokio::test] async fn test_overlay_partial_origin_policy_off_uses_whole_copy_up() -> Result<()> { let base_dir = tempdir()?; @@ -3312,50 +883,167 @@ mod tests { } #[tokio::test] - async fn test_overlay_partial_origin_unlink_cleans_metadata_and_whiteouts_base() -> Result<()> { + async fn test_overlay_partial_origin_unlink_cleans_metadata_and_whiteouts_base() -> Result<()> { + let base_dir = tempdir()?; + let delta_dir = tempdir()?; + let db_path = delta_dir.path().join("delta.db"); + let delta = AgentFS::new(db_path.to_str().unwrap()).await?; + let chunk_size = delta.chunk_size(); + let base_content = patterned_bytes(chunk_size + 19, 0xa1); + std::fs::write(base_dir.path().join("large.bin"), &base_content)?; + + let base = Arc::new(HostFS::new(base_dir.path())?); + let overlay = OverlayFS::new_with_partial_origin(base, delta, true); + overlay.init(base_dir.path().to_str().unwrap()).await?; + + let stats = overlay.lookup(ROOT_INO, "large.bin").await?.unwrap(); + let file = overlay.open(stats.ino, libc::O_RDWR).await?; + file.pwrite(chunk_size as u64 + 1, b"U").await?; + drop(file); + assert_eq!( + scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_partial_origin").await?, + 1 + ); + + overlay.unlink(ROOT_INO, "large.bin").await?; + + assert!(overlay.lookup(ROOT_INO, "large.bin").await?.is_none()); + assert_eq!( + std::fs::read(base_dir.path().join("large.bin"))?, + base_content, + "unlink should not mutate the base file" + ); + assert_eq!( + scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_partial_origin").await?, + 0, + "last unlink should remove partial-origin rows" + ); + assert_eq!( + scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_chunk_override").await?, + 0, + "last unlink should remove chunk override rows" + ); + assert_eq!( + scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_origin").await?, + 0, + "last unlink should remove origin rows" + ); + + Ok(()) + } + + #[tokio::test] + async fn overlay_reap_hook_cleans_sidecars() -> Result<()> { + let base_dir = tempdir()?; + let delta_dir = tempdir()?; + let db_path = delta_dir.path().join("delta.db"); + let delta = AgentFS::new(db_path.to_str().unwrap()).await?; + let chunk_size = delta.chunk_size(); + std::fs::write( + base_dir.path().join("src.bin"), + patterned_bytes(chunk_size + 17, 0xd1), + )?; + std::fs::write( + base_dir.path().join("dst.bin"), + patterned_bytes(chunk_size + 19, 0xe1), + )?; + + let base = Arc::new(HostFS::new(base_dir.path())?); + let overlay = OverlayFS::new_with_partial_origin(base, delta, true); + overlay.init(base_dir.path().to_str().unwrap()).await?; + + for (name, byte) in [("src.bin", b'S'), ("dst.bin", b'D')] { + let stats = overlay.lookup(ROOT_INO, name).await?.unwrap(); + let file = overlay.open(stats.ino, libc::O_RDWR).await?; + file.pwrite(3, &[byte]).await?; + file.fsync().await?; + drop(file); + } + assert_eq!( + scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_partial_origin").await?, + 2, + "both partially copied files should have sidecar metadata before rename-replace" + ); + + overlay + .rename(ROOT_INO, "src.bin", ROOT_INO, "dst.bin") + .await?; + assert_no_orphan_sidecars(&overlay, "rename-replace").await?; + + let (deferred, deferred_file) = FileSystem::create_file( + overlay.delta(), + ROOT_INO, + "manual-deferred.bin", + DEFAULT_FILE_MODE, + 0, + 0, + ) + .await?; + deferred_file.pwrite(0, b"manual").await?; + deferred_file.drain_writes().await?; + insert_manual_sidecars(overlay.delta(), deferred.ino).await?; + FileSystem::unlink(overlay.delta(), ROOT_INO, "manual-deferred.bin").await?; + assert_eq!(deferred_file.fstat().await?.nlink, 0); + drop(deferred_file); + overlay.delta().process_deferred_reaps().await?; + assert_no_orphan_sidecars(&overlay, "deferred-reap").await?; + + let crash_dir = tempdir()?; + let crash_db = crash_dir.path().join("crash.db"); + let crash_db_path = crash_db.to_string_lossy().into_owned(); + { + let agent = crate::AgentFS::open( + crate::AgentFSOptions::with_path(crash_db_path.clone()).with_base(base_dir.path()), + ) + .await?; + let (stats, file) = FileSystem::create_file( + &agent.fs, + ROOT_INO, + "manual-crash.bin", + DEFAULT_FILE_MODE, + 0, + 0, + ) + .await?; + file.pwrite(0, b"crash").await?; + file.drain_writes().await?; + insert_manual_sidecars(&agent.fs, stats.ino).await?; + FileSystem::unlink(&agent.fs, ROOT_INO, "manual-crash.bin").await?; + std::mem::forget(file); + } + + let reopened_agent = crate::AgentFS::open( + crate::AgentFSOptions::with_path(crash_db_path).with_base(base_dir.path()), + ) + .await?; + let reopened_base = Arc::new(HostFS::new(base_dir.path())?); + let reopened = OverlayFS::new_with_partial_origin(reopened_base, reopened_agent.fs, true); + reopened.load().await?; + assert_no_orphan_sidecars(&reopened, "crash-sweep").await?; + + Ok(()) + } + + #[tokio::test] + async fn overlay_sidecar_reap_hook_registers_once() -> Result<()> { let base_dir = tempdir()?; let delta_dir = tempdir()?; let db_path = delta_dir.path().join("delta.db"); let delta = AgentFS::new(db_path.to_str().unwrap()).await?; - let chunk_size = delta.chunk_size(); - let base_content = patterned_bytes(chunk_size + 19, 0xa1); - std::fs::write(base_dir.path().join("large.bin"), &base_content)?; - - let base = Arc::new(HostFS::new(base_dir.path())?); - let overlay = OverlayFS::new_with_partial_origin(base, delta, true); - overlay.init(base_dir.path().to_str().unwrap()).await?; + assert_eq!(delta.reap_hook_count(), 0); - let stats = overlay.lookup(ROOT_INO, "large.bin").await?.unwrap(); - let file = overlay.open(stats.ino, libc::O_RDWR).await?; - file.pwrite(chunk_size as u64 + 1, b"U").await?; - drop(file); - assert_eq!( - scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_partial_origin").await?, - 1 + assert!( + delta.register_reap_hook(OverlayFS::sidecar_reap_hook()), + "first constructor-time sidecar hook registration should be accepted" ); + assert_eq!(delta.reap_hook_count(), 1); - overlay.unlink(ROOT_INO, "large.bin").await?; - - assert!(overlay.lookup(ROOT_INO, "large.bin").await?.is_none()); - assert_eq!( - std::fs::read(base_dir.path().join("large.bin"))?, - base_content, - "unlink should not mutate the base file" - ); - assert_eq!( - scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_partial_origin").await?, - 0, - "last unlink should remove partial-origin rows" - ); - assert_eq!( - scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_chunk_override").await?, - 0, - "last unlink should remove chunk override rows" - ); + let base = Arc::new(HostFS::new(base_dir.path())?); + let overlay = OverlayFS::new(base, delta); assert_eq!( - scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_origin").await?, - 0, - "last unlink should remove origin rows" + overlay.delta().reap_hook_count(), + 1, + "OverlayFS construction must not duplicate a pre-registered sidecar hook" ); Ok(()) @@ -3594,6 +1282,189 @@ mod tests { Ok(()) } + #[tokio::test] + async fn test_overlay_rename_base_directory_returns_exdev_and_preserves_contents() -> Result<()> + { + let (overlay, base_dir, _delta_dir) = create_test_overlay().await?; + + let subdir_stats = overlay.lookup(ROOT_INO, "subdir").await?.unwrap(); + assert!( + subdir_stats.is_directory(), + "test fixture subdir should be a base-layer directory" + ); + + let err = overlay + .rename(ROOT_INO, "subdir", ROOT_INO, "renamed_subdir") + .await + .expect_err("renaming a base-layer directory must return EXDEV"); + match err { + Error::Fs(FsError::CrossDevice) => {} + other => panic!("expected CrossDevice/EXDEV, got {other:?}"), + } + assert_eq!(FsError::CrossDevice.to_errno(), libc::EXDEV); + + let original_stats = overlay + .lookup(ROOT_INO, "subdir") + .await? + .expect("source directory must remain visible after EXDEV"); + assert!(original_stats.is_directory()); + assert!( + overlay.lookup(ROOT_INO, "renamed_subdir").await?.is_none(), + "failed rename must not create the destination" + ); + + let nested_stats = overlay + .lookup(original_stats.ino, "nested.txt") + .await? + .expect("base child must remain visible at original path"); + let nested_file = overlay.open(nested_stats.ino, libc::O_RDONLY).await?; + assert_eq!(nested_file.pread(0, 100).await?, b"nested"); + assert_eq!( + std::fs::read(base_dir.path().join("subdir/nested.txt"))?, + b"nested", + "base backing file must remain unchanged" + ); + + Ok(()) + } + + #[tokio::test] + async fn test_overlay_rename_merged_base_dir_returns_exdev() -> Result<()> { + let base_dir = tempdir()?; + std::fs::create_dir_all(base_dir.path().join("base_dir/sub"))?; + std::fs::write(base_dir.path().join("base_dir/base.txt"), b"base root")?; + std::fs::write( + base_dir.path().join("base_dir/sub/nested.txt"), + b"base nested", + )?; + + let base = Arc::new(HostFS::new(base_dir.path())?); + + let delta_dir = tempdir()?; + let db_path = delta_dir.path().join("delta.db"); + let delta = AgentFS::new(db_path.to_str().unwrap()).await?; + + let overlay = OverlayFS::new(base, delta); + overlay.init(base_dir.path().to_str().unwrap()).await?; + + let dir_stats = overlay.lookup(ROOT_INO, "base_dir").await?.unwrap(); + assert!( + dir_stats.is_directory(), + "test fixture base_dir should be a base-origin directory" + ); + + let (_new_stats, new_file) = overlay + .create_file(dir_stats.ino, "new.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + new_file.pwrite(0, b"delta child").await?; + + let entries = overlay.readdir(dir_stats.ino).await?.unwrap(); + assert!( + entries.contains(&"base.txt".to_string()), + "merged directory should still show base child before rename" + ); + assert!( + entries.contains(&"new.txt".to_string()), + "merged directory should show delta child before rename" + ); + assert!( + entries.contains(&"sub".to_string()), + "merged directory should still show base subdirectory before rename" + ); + + let err = overlay + .rename(ROOT_INO, "base_dir", ROOT_INO, "moved") + .await + .expect_err("renaming a merged base-origin directory must return EXDEV"); + match err { + Error::Fs(FsError::CrossDevice) => {} + other => panic!("expected CrossDevice/EXDEV, got {other:?}"), + } + + let original_stats = overlay + .lookup(ROOT_INO, "base_dir") + .await? + .expect("source directory must remain visible after EXDEV"); + assert!(original_stats.is_directory()); + assert!( + overlay.lookup(ROOT_INO, "moved").await?.is_none(), + "failed merged-dir rename must not create the destination" + ); + + let base_child = overlay + .lookup(original_stats.ino, "base.txt") + .await? + .expect("base child must remain visible at original path"); + let base_file = overlay.open(base_child.ino, libc::O_RDONLY).await?; + assert_eq!(base_file.pread(0, 100).await?, b"base root"); + + let new_child = overlay + .lookup(original_stats.ino, "new.txt") + .await? + .expect("delta child must remain visible at original path"); + let new_file = overlay.open(new_child.ino, libc::O_RDONLY).await?; + assert_eq!(new_file.pread(0, 100).await?, b"delta child"); + + let subdir = overlay + .lookup(original_stats.ino, "sub") + .await? + .expect("base subdirectory must remain visible at original path"); + let nested = overlay + .lookup(subdir.ino, "nested.txt") + .await? + .expect("nested base child must remain visible at original path"); + let nested_file = overlay.open(nested.ino, libc::O_RDONLY).await?; + assert_eq!(nested_file.pread(0, 100).await?, b"base nested"); + + assert_eq!( + std::fs::read(base_dir.path().join("base_dir/base.txt"))?, + b"base root", + "base backing file must remain unchanged" + ); + assert_eq!( + std::fs::read(base_dir.path().join("base_dir/sub/nested.txt"))?, + b"base nested", + "nested base backing file must remain unchanged" + ); + + Ok(()) + } + + #[tokio::test] + async fn test_overlay_rename_delta_origin_directory_succeeds() -> Result<()> { + let (overlay, _base_dir, _delta_dir) = create_test_overlay().await?; + + let dir_stats = overlay.mkdir(ROOT_INO, "delta_dir", 0o755, 0, 0).await?; + let (_child_stats, child_file) = overlay + .create_file(dir_stats.ino, "child.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + child_file.pwrite(0, b"delta child").await?; + + overlay + .rename(ROOT_INO, "delta_dir", ROOT_INO, "moved_delta") + .await?; + + assert!( + overlay.lookup(ROOT_INO, "delta_dir").await?.is_none(), + "delta-origin source directory should be gone after rename" + ); + + let moved = overlay + .lookup(ROOT_INO, "moved_delta") + .await? + .expect("delta-origin destination directory should exist after rename"); + assert!(moved.is_directory()); + + let child = overlay + .lookup(moved.ino, "child.txt") + .await? + .expect("delta-origin child should move with the directory"); + let file = overlay.open(child.ino, libc::O_RDONLY).await?; + assert_eq!(file.pread(0, 100).await?, b"delta child"); + + Ok(()) + } + #[tokio::test] async fn test_overlay_copy_on_write_nested_file() -> Result<()> { let (overlay, base_dir, _delta_dir) = create_test_overlay().await?; @@ -3820,7 +1691,7 @@ mod tests { /// delta entry with the same name from a wrong parent. /// /// Reproduces the ENOTDIR bug: - /// 1. Base has /sdk/rust/ (directories) + /// 1. Base has /crates/agentfs-core/ (directories) /// 2. Delta has a *file* named "rust" under delta root (from some unrelated op) /// 3. lookup(sdk_ino, "rust") should return the base *directory*, not the delta file /// @@ -3829,11 +1700,11 @@ mod tests { /// Then delta.lookup(root, "rust") finds the unrelated file and returns it. #[tokio::test] async fn test_overlay_lookup_base_subdir_not_shadowed_by_wrong_delta_parent() -> Result<()> { - // Base: /sdk/rust/Cargo.toml (nested directories + file) + // Base: /crates/agentfs-core/Cargo.toml (nested directories + file) let base_dir = tempdir()?; - std::fs::create_dir_all(base_dir.path().join("sdk/rust"))?; + std::fs::create_dir_all(base_dir.path().join("crates/agentfs-core"))?; std::fs::write( - base_dir.path().join("sdk/rust/Cargo.toml"), + base_dir.path().join("crates/agentfs-core/Cargo.toml"), b"[package]\nname = \"test\"", )?; @@ -3846,28 +1717,31 @@ mod tests { let overlay = OverlayFS::new(base, delta); overlay.init(base_dir.path().to_str().unwrap()).await?; - // Create a *file* named "rust" at the overlay root (in delta). + // Create a *file* named "agentfs-core" at the overlay root (in delta). // This is the entry that could shadow the base directory if the delta // path walk uses the wrong parent inode. let (_file_stats, file) = overlay - .create_file(ROOT_INO, "rust", DEFAULT_FILE_MODE, 0, 0) + .create_file(ROOT_INO, "agentfs-core", DEFAULT_FILE_MODE, 0, 0) .await?; file.pwrite(0, b"this is a file, not a directory").await?; - // Lookup "sdk" from root — should be a base directory - let sdk_stats = overlay.lookup(ROOT_INO, "sdk").await?.unwrap(); - assert!(sdk_stats.is_directory(), "sdk should be a directory"); + // Lookup "crates" from root — should be a base directory + let crates_stats = overlay.lookup(ROOT_INO, "crates").await?.unwrap(); + assert!(crates_stats.is_directory(), "crates should be a directory"); - // Lookup "rust" under "sdk" — MUST return the base *directory*, not - // the delta *file* named "rust" that lives under root. - let rust_stats = overlay.lookup(sdk_stats.ino, "rust").await?.unwrap(); + // Lookup "agentfs-core" under "crates" — MUST return the base + // *directory*, not the delta *file* with the same name under root. + let core_stats = overlay + .lookup(crates_stats.ino, "agentfs-core") + .await? + .unwrap(); assert!( - rust_stats.is_directory(), - "sdk/rust should be a directory from base, not the file from delta root" + core_stats.is_directory(), + "crates/agentfs-core should be a directory from base, not the file from delta root" ); - // Verify we can traverse further into sdk/rust/Cargo.toml - let toml_stats = overlay.lookup(rust_stats.ino, "Cargo.toml").await?.unwrap(); + // Verify we can traverse further into crates/agentfs-core/Cargo.toml + let toml_stats = overlay.lookup(core_stats.ino, "Cargo.toml").await?.unwrap(); assert!(toml_stats.is_file(), "Cargo.toml should be a file"); Ok(()) @@ -3880,8 +1754,8 @@ mod tests { #[tokio::test] async fn test_overlay_readdir_plus_consistent_with_lookup_for_base_dir() -> Result<()> { let base_dir = tempdir()?; - std::fs::create_dir_all(base_dir.path().join("sdk/rust"))?; - std::fs::write(base_dir.path().join("sdk/rust/lib.rs"), b"fn main() {}")?; + std::fs::create_dir_all(base_dir.path().join("crates/agentfs-core"))?; + std::fs::write(base_dir.path().join("crates/agentfs-core/lib.rs"), b"fn main() {}")?; let base = Arc::new(HostFS::new(base_dir.path())?); @@ -3892,32 +1766,38 @@ mod tests { let overlay = OverlayFS::new(base, delta); overlay.init(base_dir.path().to_str().unwrap()).await?; - // Create a file named "rust" at the root in delta (wrong-parent scenario) + // Create a file named "agentfs-core" at the root in delta (wrong-parent scenario) let (_stats, file) = overlay - .create_file(ROOT_INO, "rust", DEFAULT_FILE_MODE, 0, 0) + .create_file(ROOT_INO, "agentfs-core", DEFAULT_FILE_MODE, 0, 0) .await?; file.pwrite(0, b"decoy").await?; - // Lookup "sdk" to get its overlay inode - let sdk_stats = overlay.lookup(ROOT_INO, "sdk").await?.unwrap(); + // Lookup "crates" to get its overlay inode + let crates_stats = overlay.lookup(ROOT_INO, "crates").await?.unwrap(); - // readdir_plus on "sdk" should list "rust" as a directory + // readdir_plus on "crates" should list "agentfs-core" as a directory let entries = overlay - .readdir_plus(sdk_stats.ino) + .readdir_plus(crates_stats.ino) .await? - .expect("readdir_plus should succeed on sdk"); - let rust_entry = entries.iter().find(|e| e.name == "rust"); - assert!(rust_entry.is_some(), "readdir_plus should list 'rust'"); + .expect("readdir_plus should succeed on crates"); + let core_entry = entries.iter().find(|e| e.name == "agentfs-core"); + assert!( + core_entry.is_some(), + "readdir_plus should list 'agentfs-core'" + ); assert!( - rust_entry.unwrap().stats.is_directory(), - "readdir_plus should report 'rust' as directory" + core_entry.unwrap().stats.is_directory(), + "readdir_plus should report 'agentfs-core' as directory" ); - // lookup on "sdk" for "rust" should also return a directory - let rust_lookup = overlay.lookup(sdk_stats.ino, "rust").await?.unwrap(); + // lookup on "crates" for "agentfs-core" should also return a directory + let core_lookup = overlay + .lookup(crates_stats.ino, "agentfs-core") + .await? + .unwrap(); assert!( - rust_lookup.is_directory(), - "lookup should also report 'rust' as directory (consistent with readdir_plus)" + core_lookup.is_directory(), + "lookup should report 'agentfs-core' as directory, matching readdir_plus" ); Ok(()) @@ -3985,17 +1865,23 @@ mod tests { /// returns correct types for sibling entries in the base. /// /// Scenario: - /// 1. Base has /sdk/rust/ and /sdk/python/ (two sibling dirs) - /// 2. Modify a file under /sdk/rust/ → triggers copy-up, creates - /// "sdk" and "rust" dirs in delta - /// 3. Lookup /sdk/python/ must still work (base directory) + /// 1. Base has /workspace/core/ and /workspace/tools/ (two sibling dirs) + /// 2. Modify a file under /workspace/core/ → triggers copy-up, creates + /// "workspace" and "core" dirs in delta + /// 3. Lookup /workspace/tools/ must still work (base directory) #[tokio::test] async fn test_overlay_lookup_sibling_base_dirs_after_copy_up() -> Result<()> { let base_dir = tempdir()?; - std::fs::create_dir_all(base_dir.path().join("sdk/rust"))?; - std::fs::create_dir_all(base_dir.path().join("sdk/python"))?; - std::fs::write(base_dir.path().join("sdk/rust/lib.rs"), b"fn main() {}")?; - std::fs::write(base_dir.path().join("sdk/python/main.py"), b"print('hi')")?; + std::fs::create_dir_all(base_dir.path().join("workspace/core"))?; + std::fs::create_dir_all(base_dir.path().join("workspace/tools"))?; + std::fs::write( + base_dir.path().join("workspace/core/lib.rs"), + b"fn main() {}", + )?; + std::fs::write( + base_dir.path().join("workspace/tools/main.py"), + b"print('hi')", + )?; let base = Arc::new(HostFS::new(base_dir.path())?); @@ -4006,24 +1892,24 @@ mod tests { let overlay = OverlayFS::new(base, delta); overlay.init(base_dir.path().to_str().unwrap()).await?; - // Navigate to sdk/rust/lib.rs and modify it (triggers copy-up) - let sdk_stats = overlay.lookup(ROOT_INO, "sdk").await?.unwrap(); - let rust_stats = overlay.lookup(sdk_stats.ino, "rust").await?.unwrap(); - let lib_stats = overlay.lookup(rust_stats.ino, "lib.rs").await?.unwrap(); + // Navigate to workspace/core/lib.rs and modify it (triggers copy-up) + let workspace_stats = overlay.lookup(ROOT_INO, "workspace").await?.unwrap(); + let core_stats = overlay.lookup(workspace_stats.ino, "core").await?.unwrap(); + let lib_stats = overlay.lookup(core_stats.ino, "lib.rs").await?.unwrap(); let lib_file = overlay.open(lib_stats.ino, libc::O_RDWR).await?; lib_file .pwrite(0, b"fn main() { println!(\"hello\"); }") .await?; - // Now lookup the sibling: sdk/python must still be a directory - let python_stats = overlay.lookup(sdk_stats.ino, "python").await?.unwrap(); + // Now lookup the sibling: workspace/tools must still be a directory + let tools_stats = overlay.lookup(workspace_stats.ino, "tools").await?.unwrap(); assert!( - python_stats.is_directory(), - "sdk/python should still be a directory after copy-up of sdk/rust/lib.rs" + tools_stats.is_directory(), + "workspace/tools should still be a directory after copy-up of workspace/core/lib.rs" ); - // And sdk/python/main.py must be accessible - let main_py = overlay.lookup(python_stats.ino, "main.py").await?.unwrap(); + // And workspace/tools/main.py must be accessible + let main_py = overlay.lookup(tools_stats.ino, "main.py").await?.unwrap(); assert!(main_py.is_file()); let file = overlay.open(main_py.ino, libc::O_RDONLY).await?; let content = file.pread(0, 100).await?; @@ -4032,13 +1918,13 @@ mod tests { Ok(()) } - /// Test the exact cargo scenario: path dependency at ../sdk/rust/Cargo.toml + /// Test the exact cargo scenario: path dependency at ../crates/agentfs-core/Cargo.toml /// accessed after some delta writes have occurred. #[tokio::test] async fn test_overlay_cargo_path_dependency_scenario() -> Result<()> { // Simulate the agentfs repo structure: // /cli/Cargo.toml - // /sdk/rust/Cargo.toml + // /crates/agentfs-core/Cargo.toml let base_dir = tempdir()?; std::fs::create_dir_all(base_dir.path().join("cli/src"))?; std::fs::write( @@ -4046,13 +1932,13 @@ mod tests { b"[package]\nname = \"cli\"", )?; std::fs::write(base_dir.path().join("cli/src/main.rs"), b"fn main() {}")?; - std::fs::create_dir_all(base_dir.path().join("sdk/rust/src"))?; + std::fs::create_dir_all(base_dir.path().join("crates/agentfs-core/src"))?; std::fs::write( - base_dir.path().join("sdk/rust/Cargo.toml"), + base_dir.path().join("crates/agentfs-core/Cargo.toml"), b"[package]\nname = \"sdk\"", )?; std::fs::write( - base_dir.path().join("sdk/rust/src/lib.rs"), + base_dir.path().join("crates/agentfs-core/src/lib.rs"), b"pub fn hello() {}", )?; @@ -4069,18 +1955,21 @@ mod tests { let cli_stats = overlay.lookup(ROOT_INO, "cli").await?.unwrap(); let _target_stats = overlay.mkdir(cli_stats.ino, "target", 0o755, 0, 0).await?; - // Now simulate cargo resolving ../sdk/rust/Cargo.toml + // Now simulate cargo resolving ../crates/agentfs-core/Cargo.toml // This is the path that fails with ENOTDIR in the bug report - let sdk_stats = overlay.lookup(ROOT_INO, "sdk").await?.unwrap(); - assert!(sdk_stats.is_directory(), "sdk must be a directory"); + let crates_stats = overlay.lookup(ROOT_INO, "crates").await?.unwrap(); + assert!(crates_stats.is_directory(), "crates must be a directory"); - let rust_stats = overlay.lookup(sdk_stats.ino, "rust").await?.unwrap(); + let core_stats = overlay + .lookup(crates_stats.ino, "agentfs-core") + .await? + .unwrap(); assert!( - rust_stats.is_directory(), - "sdk/rust must be a directory (ENOTDIR bug)" + core_stats.is_directory(), + "crates/agentfs-core must be a directory (ENOTDIR bug)" ); - let toml_stats = overlay.lookup(rust_stats.ino, "Cargo.toml").await?.unwrap(); + let toml_stats = overlay.lookup(core_stats.ino, "Cargo.toml").await?.unwrap(); assert!(toml_stats.is_file(), "Cargo.toml must be a file"); // Also verify reading the file works @@ -4878,6 +2767,156 @@ mod tests { Ok(()) } + #[tokio::test] + async fn overlay_lookup_forget_prunes_maps() -> Result<()> { + let base_dir = tempdir()?; + for index in 0..128 { + std::fs::write( + base_dir.path().join(format!("file-{index:03}.txt")), + format!("base-{index:03}"), + )?; + } + + let base = Arc::new(HostFS::new(base_dir.path())?); + let delta_dir = tempdir()?; + let db_path = delta_dir.path().join("delta.db"); + let delta = AgentFS::new(db_path.to_str().unwrap()).await?; + let overlay = OverlayFS::new(base, delta); + overlay.init(base_dir.path().to_str().unwrap()).await?; + + let baseline = overlay.debug_map_counts(); + let mut looked_up = Vec::new(); + for index in 0..128 { + let name = format!("file-{index:03}.txt"); + let stats = overlay.lookup(ROOT_INO, &name).await?.unwrap(); + looked_up.push(stats.ino); + } + + let peak = overlay.debug_map_counts(); + assert!( + peak.inode_entries >= baseline.inode_entries + looked_up.len(), + "lookup should create overlay mappings; baseline={baseline:?}, peak={peak:?}" + ); + + for ino in looked_up { + overlay.forget(ino, 1).await; + } + + let final_counts = overlay.debug_map_counts(); + assert_eq!( + final_counts, baseline, + "FORGET should prune lookup-only overlay mappings back to baseline" + ); + + Ok(()) + } + + #[tokio::test] + async fn overlay_load_origins_propagates_select_errors() -> Result<()> { + let (overlay, _base_dir, _delta_dir) = create_test_overlay().await?; + let conn = overlay.delta().get_connection().await?; + conn.execute("DROP TABLE fs_origin", ()).await?; + + let result = overlay.load().await; + assert!( + result.is_err(), + "load_origins must propagate a failed fs_origin SELECT" + ); + + Ok(()) + } + + #[tokio::test] + async fn overlay_whiteout_failures_roll_back() -> Result<()> { + let base_dir = tempdir()?; + std::fs::write(base_dir.path().join("unlink.txt"), b"unlink")?; + std::fs::create_dir(base_dir.path().join("empty-dir"))?; + std::fs::write(base_dir.path().join("rename-src.txt"), b"rename")?; + std::fs::write(base_dir.path().join("rename-dst.txt"), b"destination")?; + + let base = Arc::new(HostFS::new(base_dir.path())?); + let delta_dir = tempdir()?; + let db_path = delta_dir.path().join("delta.db"); + let delta = AgentFS::new(db_path.to_str().unwrap()).await?; + let overlay = OverlayFS::new(base, delta); + overlay.init(base_dir.path().to_str().unwrap()).await?; + + overlay.fail_next_whiteout_for_test("unlink injected whiteout failure"); + let unlink_result = overlay.unlink(ROOT_INO, "unlink.txt").await; + assert!(unlink_result.is_err()); + assert!( + overlay.lookup(ROOT_INO, "unlink.txt").await?.is_some(), + "failed unlink must leave the base path visible" + ); + assert_eq!( + scalar_i64( + &overlay, + "SELECT COUNT(*) FROM fs_whiteout WHERE path = '/unlink.txt'" + ) + .await?, + 0, + "failed unlink must not leave a half-applied whiteout" + ); + + overlay.fail_next_whiteout_for_test("rmdir injected whiteout failure"); + let rmdir_result = overlay.rmdir(ROOT_INO, "empty-dir").await; + assert!(rmdir_result.is_err()); + assert!( + overlay.lookup(ROOT_INO, "empty-dir").await?.is_some(), + "failed rmdir must leave the base directory visible" + ); + assert_eq!( + scalar_i64( + &overlay, + "SELECT COUNT(*) FROM fs_whiteout WHERE path = '/empty-dir'", + ) + .await?, + 0, + "failed rmdir must not leave a half-applied whiteout" + ); + + overlay.unlink(ROOT_INO, "rename-dst.txt").await?; + assert!(overlay.lookup(ROOT_INO, "rename-dst.txt").await?.is_none()); + assert_eq!( + scalar_i64( + &overlay, + "SELECT COUNT(*) FROM fs_whiteout WHERE path = '/rename-dst.txt'", + ) + .await?, + 1 + ); + + overlay.fail_next_whiteout_for_test("rename injected whiteout failure"); + let rename_result = overlay + .rename(ROOT_INO, "rename-src.txt", ROOT_INO, "rename-dst.txt") + .await; + assert!(rename_result.is_err()); + assert!( + overlay.lookup(ROOT_INO, "rename-src.txt").await?.is_some(), + "failed rename must leave the source path visible" + ); + assert!( + overlay.lookup(ROOT_INO, "rename-dst.txt").await?.is_none(), + "failed rename must preserve the destination whiteout" + ); + assert_eq!( + scalar_i64( + &overlay, + "SELECT COUNT(*) FROM fs_whiteout WHERE path = '/rename-dst.txt'", + ) + .await?, + 1, + "failed rename must roll back the attempted whiteout removal" + ); + assert_eq!( + scalar_i64(&overlay, "SELECT COUNT(*) FROM fs_origin").await?, + 0, + "failed rename must not copy up the source before the whiteout transaction succeeds" + ); + + Ok(()) + } + async fn scalar_i64(overlay: &OverlayFS, sql: &str) -> Result { let conn = overlay.delta().get_connection().await?; let mut rows = conn.query(sql, ()).await?; @@ -4892,6 +2931,60 @@ mod tests { .unwrap_or(0)) } + async fn insert_manual_sidecars(delta: &AgentFS, ino: i64) -> Result<()> { + let conn = delta.get_connection().await?; + conn.execute( + "INSERT OR REPLACE INTO fs_origin (delta_ino, base_ino) VALUES (?, ?)", + (ino, 9_001_i64), + ) + .await?; + conn.execute( + "INSERT OR REPLACE INTO fs_partial_origin \ + (delta_ino, base_ino, base_path, base_size, created_at) \ + VALUES (?, ?, ?, ?, ?)", + (ino, 9_001_i64, format!("/manual-{ino}"), 0_i64, 0_i64), + ) + .await?; + conn.execute( + "INSERT OR REPLACE INTO fs_chunk_override (delta_ino, chunk_index) VALUES (?, ?)", + (ino, 0_i64), + ) + .await?; + Ok(()) + } + + async fn assert_no_orphan_sidecars(overlay: &OverlayFS, context: &str) -> Result<()> { + let probes = [ + ( + "fs_origin", + "SELECT COUNT(*) FROM fs_origin s \ + LEFT JOIN fs_inode i ON i.ino = s.delta_ino \ + WHERE i.ino IS NULL", + ), + ( + "fs_partial_origin", + "SELECT COUNT(*) FROM fs_partial_origin s \ + LEFT JOIN fs_inode i ON i.ino = s.delta_ino \ + WHERE i.ino IS NULL", + ), + ( + "fs_chunk_override", + "SELECT COUNT(*) FROM fs_chunk_override s \ + LEFT JOIN fs_inode i ON i.ino = s.delta_ino \ + WHERE i.ino IS NULL", + ), + ]; + + for (table, sql) in probes { + assert_eq!( + scalar_i64(overlay, sql).await?, + 0, + "{context} should not leave orphan rows in {table}" + ); + } + Ok(()) + } + fn patterned_bytes(len: usize, seed: u8) -> Vec { (0..len) .map(|index| { @@ -4900,4 +2993,3 @@ mod tests { }) .collect() } -} diff --git a/crates/agentfs-core/src/fs/overlay/whiteouts.rs b/crates/agentfs-core/src/fs/overlay/whiteouts.rs new file mode 100644 index 00000000..d5321542 --- /dev/null +++ b/crates/agentfs-core/src/fs/overlay/whiteouts.rs @@ -0,0 +1,137 @@ +use super::*; +use turso::transaction::{Transaction, TransactionBehavior}; + +fn parent_path_for_whiteout(path: &str) -> String { + if path == "/" { + return "/".to_string(); + } + + let trimmed = path.trim_end_matches('/'); + match trimmed.rfind('/') { + Some(0) | None => "/".to_string(), + Some(index) => trimmed[..index].to_string(), + } +} + +impl OverlayFS { + /// Check if a path is whiteout (deleted from base). + pub(super) fn is_whiteout(&self, path: &str) -> bool { + let whiteouts = self.whiteouts.read(); + // Check path and all ancestors. + let mut current = String::new(); + for component in path.split('/').filter(|s| !s.is_empty()) { + current = format!("{current}/{component}"); + if whiteouts.contains(¤t) { + return true; + } + } + false + } + + /// Create a whiteout for a path. + pub(super) async fn create_whiteout(&self, path: &str) -> Result<()> { + let conn = self.delta.get_connection().await?; + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let parent_path = parent_path_for_whiteout(path); + let (now, _) = current_timestamp()?; + + let result: Result<()> = async { + conn.execute( + "INSERT OR REPLACE INTO fs_whiteout (path, parent_path, created_at) VALUES (?, ?, ?)", + (path, parent_path, now), + ) + .await?; + self.maybe_fail_whiteout_for_test()?; + Ok(()) + } + .await; + + match result { + Ok(()) => { + txn.commit().await?; + self.whiteouts.write().insert(path.to_string()); + Ok(()) + } + Err(error) => { + let _ = txn.rollback().await; + Err(error) + } + } + } + + /// Remove a whiteout. + pub(super) async fn remove_whiteout(&self, path: &str) -> Result<()> { + if !self.whiteouts.read().contains(path) { + return Ok(()); + } + + let conn = self.delta.get_connection().await?; + let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; + let result: Result<()> = async { + conn.execute("DELETE FROM fs_whiteout WHERE path = ?", (path,)) + .await?; + self.maybe_fail_whiteout_for_test()?; + Ok(()) + } + .await; + + match result { + Ok(()) => { + txn.commit().await?; + self.whiteouts.write().remove(path); + Ok(()) + } + Err(error) => { + let _ = txn.rollback().await; + Err(error) + } + } + } + + /// Get child whiteouts for a directory. + pub(super) fn get_child_whiteouts(&self, dir_path: &str) -> HashSet { + let whiteouts = self.whiteouts.read(); + let prefix = if dir_path == "/" { + "/".to_string() + } else { + format!("{dir_path}/") + }; + whiteouts + .iter() + .filter_map(|p| { + if dir_path == "/" { + let trimmed = p.trim_start_matches('/'); + if !trimmed.contains('/') { + Some(trimmed.to_string()) + } else { + None + } + } else if p.starts_with(&prefix) { + let rest = &p[prefix.len()..]; + if !rest.contains('/') { + Some(rest.to_string()) + } else { + None + } + } else { + None + } + }) + .collect() + } + + #[cfg(test)] + pub(super) fn fail_next_whiteout_for_test(&self, reason: &str) { + *self.whiteout_fault.lock() = Some(reason.to_string()); + } + + fn maybe_fail_whiteout_for_test(&self) -> Result<()> { + #[cfg(test)] + { + if let Some(reason) = self.whiteout_fault.lock().take() { + return Err(Error::Internal(reason)); + } + } + Ok(()) + } +} diff --git a/sdk/rust/src/kvstore.rs b/crates/agentfs-core/src/kv.rs similarity index 83% rename from sdk/rust/src/kvstore.rs rename to crates/agentfs-core/src/kv.rs index a69b88ca..26b45f45 100644 --- a/sdk/rust/src/kvstore.rs +++ b/crates/agentfs-core/src/kv.rs @@ -1,5 +1,6 @@ -use crate::connection_pool::ConnectionPool; use crate::error::Result; +use crate::pool::{ConnectionPool, DatabaseType}; +use crate::schema; use serde::{Deserialize, Serialize}; use turso::Builder; @@ -13,7 +14,12 @@ impl KvStore { /// Create a new KV store pub async fn new(db_path: &str) -> Result { let db = Builder::new_local(db_path).build().await?; - let pool = ConnectionPool::new(db); + let options = if db_path == ":memory:" { + crate::fs::agentfs::memory_connection_pool_options() + } else { + crate::fs::agentfs::file_backed_connection_pool_options() + }; + let pool = ConnectionPool::with_options(DatabaseType::Local(db), options); let kv = Self { pool }; kv.initialize().await?; Ok(kv) @@ -29,25 +35,7 @@ impl KvStore { /// Initialize the database schema async fn initialize(&self) -> Result<()> { let conn = self.pool.get_connection().await?; - conn.execute( - "CREATE TABLE IF NOT EXISTS kv_store ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - created_at INTEGER DEFAULT (unixepoch()), - updated_at INTEGER DEFAULT (unixepoch()) - )", - (), - ) - .await?; - - conn.execute( - "CREATE INDEX IF NOT EXISTS idx_kv_store_created_at - ON kv_store(created_at)", - (), - ) - .await?; - - Ok(()) + schema::require_current(&conn).await } /// Set a key-value pair diff --git a/sdk/rust/src/lib.rs b/crates/agentfs-core/src/lib.rs similarity index 69% rename from sdk/rust/src/lib.rs rename to crates/agentfs-core/src/lib.rs index b23850a2..4bc8afbc 100644 --- a/sdk/rust/src/lib.rs +++ b/crates/agentfs-core/src/lib.rs @@ -1,15 +1,46 @@ -pub mod connection_pool; +//! AgentFS core: the SQLite-backed virtual filesystem engine. +//! +//! This is the only externally consumed crate. It owns the storage engine +//! (chunk/inline layout per docs/SPEC.md), the write batcher, inode +//! lifecycle and reap hooks, the overlay layer (whiteouts, origin tracking, +//! the partial-origin policy), scoped host-FS reads, schema authority +//! (`user_version` migrations plus the integrity battery), the typed config +//! system parsed at the crate edge (`config::EnvReader`), the telemetry +//! registry with its single report sink, and the `semantics` facade +//! (access, durability, handles) that the transport adapter crates build on. +//! +//! Owned invariants: +//! +//! - All virtual filesystem state lives in the single AgentFS SQLite +//! database. Sandboxed writes never touch the host filesystem; overlay +//! reads are scoped to the configured read-only base directory. +//! - Buffered (volatile-ack) writes are acceleration state only: durable +//! acks (`AckDurability::Committed`, commit barriers, shutdown finalize) +//! return only after the bytes are committed to SQLite, metadata reads +//! merge pending state, and deletions discard it. +//! - Errors are typed (`FsError` at the trait, `Error` above it); row +//! decoding never fabricates defaults for corrupt data. +//! - Environment variables are read only inside `config`; everything +//! downstream receives values. + +pub mod config; pub mod error; -pub mod filesystem; -pub mod kvstore; -pub mod profiling; +pub mod fs; +pub mod kv; +pub mod mounts; +pub mod options; +pub mod pool; pub mod schema; +pub mod semantics; +pub mod telemetry; pub mod toolcalls; use error::{Error, Result}; +use pool::{ConnectionPool, DatabaseType, PoolOptions, PooledConnection}; use std::{ collections::{HashSet, VecDeque}, - path::{Path, PathBuf}, + path::PathBuf, + sync::Arc, }; use turso::{Builder, EncryptionOpts, Value}; @@ -17,258 +48,36 @@ use turso::{Builder, EncryptionOpts, Value}; pub use turso::sync::{DatabaseSyncStats, PartialBootstrapStrategy, PartialSyncOpts}; // Re-export filesystem types +pub use config::{ + BatcherConfig, CoreConfig, EnvReader, Geometry, DEFAULT_WRITE_BATCH_BYTES, + DEFAULT_WRITE_BATCH_GLOBAL_BYTES, DEFAULT_WRITE_BATCH_MS, DEFAULT_WRITE_BATCH_TXN_BYTES, + DEFAULT_WRITE_BATCH_TXN_INODES, +}; #[cfg(any(target_os = "linux", target_os = "macos"))] -pub use filesystem::HostFS; -pub use filesystem::{ +pub use fs::HostFS; +pub use fs::{ BoxedFile, DirEntry, File, FileSystem, FilesystemStats, FsError, ImportEntry, ImportOptions, ImportSession, ImportedEntry, OverlayFS, PartialOriginMode, PartialOriginPolicy, Stats, TimeChange, WriteRange, DEFAULT_DIR_MODE, DEFAULT_FILE_MODE, DEFAULT_PARTIAL_ORIGIN_THRESHOLD_BYTES, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, }; -pub use kvstore::KvStore; -pub use schema::{SchemaVersion, AGENTFS_SCHEMA_VERSION}; +pub use kv::KvStore; +pub use mounts::{get_mounts, Mount}; +pub use options::{agentfs_dir, AgentFSOptions, EncryptionConfig, SyncOptions}; +pub use schema::{SchemaVersion, AGENTFS_SCHEMA_VERSION, CURRENT}; +pub use semantics::{AckDurability, Semantics, WriteReceipt}; pub use toolcalls::{ToolCall, ToolCallStats, ToolCallStatus, ToolCalls}; -/// Directory containing agentfs databases -pub fn agentfs_dir() -> &'static std::path::Path { - std::path::Path::new(".agentfs") -} - -/// Information about a mounted agentfs filesystem -#[derive(Debug, Clone)] -pub struct Mount { - /// The ID (from the mount source, e.g., "agentfs:my-agent" -> "my-agent") - pub id: String, - /// The mountpoint path - pub mountpoint: PathBuf, -} - -/// Get all currently mounted agentfs filesystems by parsing /proc/mounts -/// -/// This is the authoritative source for mount information - if it's in /proc/mounts, -/// it's mounted. If not, it's not. No stale state possible. -#[cfg(target_os = "linux")] -pub fn get_mounts() -> Vec { - let Ok(contents) = std::fs::read_to_string("/proc/mounts") else { - return vec![]; - }; - contents - .lines() - .filter_map(|line| { - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() >= 2 && parts[0].starts_with("agentfs:") { - let agent_id = parts[0].strip_prefix("agentfs:")?.to_string(); - // Skip the internal "fuse" mount used by the daemon - if agent_id == "fuse" { - return None; - } - Some(Mount { - id: agent_id, - mountpoint: PathBuf::from(parts[1]), - }) - } else { - None - } - }) - .collect() -} - -/// Get all currently mounted agentfs filesystems (non-Linux stub) -#[cfg(not(target_os = "linux"))] -pub fn get_mounts() -> Vec { - // On macOS, we could parse the output of `mount` command - // For now, return empty - can be implemented later - vec![] -} - -/// Configuration options for sync -#[derive(Debug, Clone, Default)] -pub struct SyncOptions { - /// Remote URL for syncing - pub remote_url: Option, - /// Auth token for remote sync - pub auth_token: Option, - /// Partial sync options - pub partial_sync: Option, -} - -/// Configuration options for local encryption -#[derive(Debug, Clone)] -pub struct EncryptionConfig { - /// Hex-encoded encryption key - pub hex_key: String, - /// Cipher algorithm (e.g., "aegis256", "aegis128l", "aes256gcm" etc) - pub cipher: String, -} - -/// Configuration options for opening an AgentFS instance -#[derive(Debug, Clone, Default)] -pub struct AgentFSOptions { - /// Optional unique identifier for the agent. - /// - If Some(id): Creates persistent storage at `.agentfs/{id}.db` - /// - If None: Uses ephemeral in-memory database - pub id: Option, - /// Optional custom path to the database file. - /// Takes precedence over `id` if both are set. - pub path: Option, - /// Optional base directory for overlay filesystem (copy-on-write). - /// When set, the filesystem operates as an overlay on top of this directory. - pub base: Option, - /// Sync options for remote database synchronization - pub sync: SyncOptions, - /// Encryption configuration for database at rest - pub encryption: Option, -} - -impl AgentFSOptions { - /// Validates an agent ID to prevent path traversal and ensure safe filesystem operations. - /// Returns true if the ID contains only alphanumeric characters, hyphens, and underscores. - pub fn validate_agent_id(id: &str) -> bool { - !id.is_empty() - && id - .chars() - .all(|c| c.is_alphanumeric() || c == '-' || c == '_') - } - pub fn db_path(&self) -> Result { - // Determine database path: path takes precedence over id - if let Some(path) = &self.path { - // Custom path provided directly - Ok(path.to_string()) - } else if let Some(id) = &self.id { - // Validate agent ID to prevent path traversal attacks - if !Self::validate_agent_id(id) { - return Err(Error::InvalidAgentId(id.clone())); - } - - // Ensure .agentfs directory exists - let agentfs_dir = agentfs_dir(); - if !agentfs_dir.exists() { - std::fs::create_dir_all(agentfs_dir)?; - } - Ok(format!("{}/{}.db", agentfs_dir.display(), id)) - } else { - // No id or path = ephemeral in-memory database - Ok(":memory:".to_string()) - } - } - /// Create options for a persistent agent with the given ID - pub fn with_id(id: impl Into) -> Self { - Self { - id: Some(id.into()), - path: None, - base: None, - sync: SyncOptions::default(), - encryption: None, - } - } - - /// Create options for an ephemeral in-memory agent - pub fn ephemeral() -> Self { - Self { - id: None, - path: None, - base: None, - sync: SyncOptions::default(), - encryption: None, - } - } - - /// Create options with a custom database path - pub fn with_path(path: impl Into) -> Self { - Self { - id: None, - path: Some(path.into()), - base: None, - sync: SyncOptions::default(), - encryption: None, - } - } - - /// Set sync options - pub fn with_sync(mut self, sync: SyncOptions) -> Self { - self.sync = sync; - self - } - - /// Set the base directory for overlay filesystem (copy-on-write) - pub fn with_base(mut self, base: impl Into) -> Self { - self.base = Some(base.into()); - self - } - - /// Enable local encryption with a hex-encoded key and cipher - /// - /// # Arguments - /// * `hex_key` - Hex-encoded encryption key (64 chars for 256-bit ciphers, 32 for 128-bit) - /// * `cipher` - Cipher algorithm (e.g., "aegis256", "aes256gcm" etc) - pub fn with_encryption_key(mut self, hex_key: &str, cipher: &str) -> Self { - self.encryption = Some(EncryptionConfig { - hex_key: hex_key.to_string(), - cipher: cipher.to_string(), - }); - self - } - - /// Set encryption configuration directly - pub fn with_encryption(mut self, encryption: EncryptionConfig) -> Self { - self.encryption = Some(encryption); - self - } - - /// Resolve an id-or-path string to AgentFSOptions - /// - /// Resolution order (first match wins): - /// 1. `:memory:` -> ephemeral in-memory database - /// 2. Valid agent ID with existing `.agentfs/{id}.db` -> uses that agent - /// 3. Existing file path -> uses that path directly - /// - /// Returns an error if neither an agent nor a file exists. - pub fn resolve(id_or_path: impl Into) -> Result { - let id_or_path = id_or_path.into(); - - if id_or_path == ":memory:" { - return Ok(Self::ephemeral()); - } - - // First, check if it's a valid agent ID with an existing database in .agentfs/ - if AgentFSOptions::validate_agent_id(&id_or_path) { - let db_path = agentfs_dir().join(format!("{}.db", id_or_path)); - if db_path.exists() { - return Ok(Self::with_path(db_path.to_str().ok_or_else(|| { - Error::InvalidUtf8Path(db_path.display().to_string()) - })?)); - } - } - - // Fall back to treating as a direct file path - let path = Path::new(&id_or_path); - if path.is_file() { - Ok(Self::with_path(id_or_path)) - } else if AgentFSOptions::validate_agent_id(&id_or_path) { - // Not a valid agent and not an existing file - Err(Error::AgentNotFound { - id: id_or_path.clone(), - path: agentfs_dir() - .join(format!("{}.db", id_or_path)) - .display() - .to_string(), - }) - } else { - Err(Error::InvalidAgentId(id_or_path)) - } - } -} - /// The main AgentFS SDK struct /// /// This provides a unified interface to the filesystem, key-value store, /// and tool calls tracking backed by a SQLite database. pub struct AgentFS { - pool: connection_pool::ConnectionPool, + pool: ConnectionPool, sync_db: Option, pub kv: KvStore, - pub fs: filesystem::AgentFS, + pub fs: fs::AgentFS, pub tools: ToolCalls, } @@ -280,9 +89,9 @@ impl AgentFS { /// /// # Examples /// ```no_run - /// use agentfs_sdk::{AgentFS, AgentFSOptions}; + /// use agentfs_core::{AgentFS, AgentFSOptions}; /// - /// # async fn example() -> agentfs_sdk::error::Result<()> { + /// # async fn example() -> agentfs_core::error::Result<()> { /// // Persistent storage /// let agent = AgentFS::open(AgentFSOptions::with_id("my-agent")).await?; /// @@ -292,6 +101,11 @@ impl AgentFS { /// # } /// ``` pub async fn open(options: AgentFSOptions) -> Result { + let core_config = options + .core_config + .clone() + .unwrap_or_else(CoreConfig::from_env); + // Validate base directory if provided if let Some(ref path) = options.base { if !path.exists() { @@ -327,7 +141,10 @@ impl AgentFS { builder = builder.with_partial_sync_opts_experimental(partial_sync); } let db = builder.build().await?; - let pool = connection_pool::ConnectionPool::new_sync(db.clone()); + let pool = ConnectionPool::with_options( + DatabaseType::Sync(db.clone()), + PoolOptions::single_connection(), + ); (Some(db), pool) } else if std::fs::exists(&meta_path).unwrap_or(false) { let mut builder = turso::sync::Builder::new_remote(&db_path); @@ -335,7 +152,10 @@ impl AgentFS { builder = builder.with_auth_token(auth_token); } let db = builder.build().await?; - let pool = connection_pool::ConnectionPool::new_sync(db.clone()); + let pool = ConnectionPool::with_options( + DatabaseType::Sync(db.clone()), + PoolOptions::single_connection(), + ); (Some(db), pool) } else { let db = if let Some(ref enc_config) = options.encryption { @@ -351,27 +171,40 @@ impl AgentFS { Builder::new_local(&db_path).build().await? }; let pool = if db_path == ":memory:" { - connection_pool::ConnectionPool::new_single_connection(db) + ConnectionPool::with_options( + DatabaseType::Local(db), + fs::agentfs::memory_connection_pool_options(), + ) } else { - connection_pool::ConnectionPool::with_options( - db, - filesystem::agentfs::file_backed_connection_pool_options(), + ConnectionPool::with_options( + DatabaseType::Local(db), + fs::agentfs::file_backed_connection_pool_options(), ) }; (None, pool) }; - // Check schema version for existing databases - let conn = pool.get_connection().await?; - schema::check_schema_version(&conn).await?; + // Initialize or normalize schema for existing databases before any + // schema-owned callers read or write sidecar sections. Old schema + // versions are rejected here; upgrades are `agentfs migrate`'s job. + let mut conn = pool.get_connection().await?; + if let Err(error) = schema::require_current(&conn).await { + conn.mark_unhealthy_if_fatal(&error); + return Err(error); + } drop(conn); + let overlay_requested = options.base.is_some(); + // Initialize overlay schema if base is provided if let Some(base_path) = options.base { let canonical_base = std::fs::canonicalize(base_path)?; let base_path_str = canonical_base.to_string_lossy().to_string(); - let conn = pool.get_connection().await?; - OverlayFS::init_schema(&conn, &base_path_str).await?; + let mut conn = pool.get_connection().await?; + if let Err(error) = OverlayFS::init_schema(&conn, &base_path_str).await { + conn.mark_unhealthy_if_fatal(&error); + return Err(error); + } } let db_path_for_fs = if sync_db.is_none() && db_path != ":memory:" { @@ -380,16 +213,24 @@ impl AgentFS { None }; - Self::open_with_pool_and_path(pool, sync_db, db_path_for_fs).await + let reap_hooks = Self::overlay_reap_hooks_for_pool(&pool, overlay_requested).await?; + Self::build_from_pool_and_path(pool, sync_db, db_path_for_fs, core_config, reap_hooks).await } - /// Open an AgentFS instance from a connection pool - pub async fn open_with_pool( - pool: connection_pool::ConnectionPool, + async fn build_from_pool_and_config( + pool: ConnectionPool, sync_db: Option, + core_config: CoreConfig, ) -> Result { + let reap_hooks = Self::overlay_reap_hooks_for_pool(&pool, false).await?; let kv = KvStore::from_pool(pool.clone()).await?; - let fs = filesystem::AgentFS::from_pool(pool.clone()).await?; + let fs = fs::AgentFS::from_pool_with_path_config_and_reap_hooks( + pool.clone(), + None, + core_config, + reap_hooks, + ) + .await?; let tools = ToolCalls::from_pool(pool.clone()).await?; Ok(Self { @@ -401,13 +242,21 @@ impl AgentFS { }) } - async fn open_with_pool_and_path( - pool: connection_pool::ConnectionPool, + async fn build_from_pool_and_path( + pool: ConnectionPool, sync_db: Option, db_path: Option, + core_config: CoreConfig, + reap_hooks: Vec>, ) -> Result { let kv = KvStore::from_pool(pool.clone()).await?; - let fs = filesystem::AgentFS::from_pool_with_path(pool.clone(), db_path).await?; + let fs = fs::AgentFS::from_pool_with_path_config_and_reap_hooks( + pool.clone(), + db_path, + core_config, + reap_hooks, + ) + .await?; let tools = ToolCalls::from_pool(pool.clone()).await?; Ok(Self { @@ -419,10 +268,36 @@ impl AgentFS { }) } - /// Open an AgentFS instance from a sync database - pub async fn open_with_sync_db(db: turso::sync::Database) -> Result { - let pool = connection_pool::ConnectionPool::new_sync(db.clone()); - Self::open_with_pool(pool, Some(db)).await + async fn overlay_reap_hooks_for_pool( + pool: &ConnectionPool, + force_overlay_hook: bool, + ) -> Result>> { + if force_overlay_hook || Self::overlay_config_exists(pool).await? { + Ok(vec![fs::OverlayFS::sidecar_reap_hook()]) + } else { + Ok(Vec::new()) + } + } + + async fn overlay_config_exists(pool: &ConnectionPool) -> Result { + let conn = pool.get_connection().await?; + let mut rows = conn + .query( + "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'fs_overlay_config'", + (), + ) + .await?; + if rows.next().await?.is_none() { + return Ok(false); + } + + let mut rows = conn + .query( + "SELECT 1 FROM fs_overlay_config WHERE key = 'base_path' LIMIT 1", + (), + ) + .await?; + Ok(rows.next().await?.is_some()) } /// Create a new AgentFS instance (deprecated, use `open` instead) @@ -436,23 +311,26 @@ impl AgentFS { pub async fn new(db_path: &str) -> Result { let db = Builder::new_local(db_path).build().await?; let pool = if db_path == ":memory:" { - connection_pool::ConnectionPool::new_single_connection(db) + ConnectionPool::with_options( + DatabaseType::Local(db), + fs::agentfs::memory_connection_pool_options(), + ) } else { - connection_pool::ConnectionPool::with_options( - db, - filesystem::agentfs::file_backed_connection_pool_options(), + ConnectionPool::with_options( + DatabaseType::Local(db), + fs::agentfs::file_backed_connection_pool_options(), ) }; - Self::open_with_pool(pool, None).await + Self::build_from_pool_and_config(pool, None, CoreConfig::from_env()).await } /// Get a connection from the pool - pub async fn get_connection(&self) -> Result { + pub async fn get_connection(&self) -> Result { self.pool.get_connection().await } /// Get the connection pool - pub fn get_pool(&self) -> connection_pool::ConnectionPool { + pub fn get_pool(&self) -> ConnectionPool { self.pool.clone() } @@ -461,11 +339,6 @@ impl AgentFS { self.sync_db.is_some() } - /// Get the underlying sync database (if sync is enabled) - pub fn get_sync_db(&self) -> Option<&turso::sync::Database> { - self.sync_db.as_ref() - } - /// Pull changes from remote database pub async fn pull(&self) -> Result<()> { let db = self.sync_db.as_ref().ok_or(Error::SyncNotEnabled)?; @@ -837,18 +710,24 @@ mod tests { #[test] fn test_resolve_invalid_agent_id() { - // Agent IDs with path traversal should be rejected + // Path traversal is path-shaped: rejected as a missing database, not + // as a malformed agent ID. let result = AgentFSOptions::resolve("../evil"); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("invalid agent ID")); + assert!(result + .unwrap_err() + .to_string() + .contains("database not found")); // Agent IDs with spaces should be rejected let result = AgentFSOptions::resolve("invalid agent"); assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("invalid agent ID")); // Agent IDs with special characters should be rejected let result = AgentFSOptions::resolve("agent@test"); assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("invalid agent ID")); } #[test] @@ -858,6 +737,51 @@ mod tests { assert!(result.unwrap_err().to_string().contains("not found")); } + #[test] + fn test_resolve_nonexistent_path_reports_database_not_found() { + for arg in [ + "/definitely/missing.db", + "definitely-missing-dir/agent.db", + "definitely-missing.db", + ] { + let err = AgentFSOptions::resolve(arg).unwrap_err(); + let message = err.to_string(); + assert!( + message.contains("database not found") && message.contains(arg), + "path-shaped argument {arg:?} must report a missing database, got: {message}" + ); + } + } + + #[test] + fn test_db_path_is_absolute() { + // Mount teardown chdirs the process to `/`; a relative db path handed + // to turso would make every later by-path operation resolve wrong. + let by_path = AgentFSOptions::with_path("some-dir/relative.db") + .db_path() + .unwrap(); + assert!( + std::path::Path::new(&by_path).is_absolute(), + "with_path must absolutize: {by_path}" + ); + assert!(by_path.ends_with("/some-dir/relative.db")); + + let by_id = AgentFSOptions::with_id("db-path-absolute-test") + .db_path() + .unwrap(); + assert!( + std::path::Path::new(&by_id).is_absolute(), + "with_id must absolutize: {by_id}" + ); + assert!(by_id.ends_with("/.agentfs/db-path-absolute-test.db")); + + assert_eq!(AgentFSOptions::ephemeral().db_path().unwrap(), ":memory:"); + assert_eq!( + AgentFSOptions::with_path(":memory:").db_path().unwrap(), + ":memory:" + ); + } + #[test] fn test_resolve_valid_agent_id_formats() { // Setup: create .agentfs directory and test databases diff --git a/crates/agentfs-core/src/mounts.rs b/crates/agentfs-core/src/mounts.rs new file mode 100644 index 00000000..6d755f01 --- /dev/null +++ b/crates/agentfs-core/src/mounts.rs @@ -0,0 +1,48 @@ +use std::path::PathBuf; + +/// Information about a mounted agentfs filesystem +#[derive(Debug, Clone)] +pub struct Mount { + /// The ID (from the mount source, e.g., "agentfs:my-agent" -> "my-agent") + pub id: String, + /// The mountpoint path + pub mountpoint: PathBuf, +} + +/// Get all currently mounted agentfs filesystems by parsing /proc/mounts +/// +/// This is the authoritative source for mount information - if it's in /proc/mounts, +/// it's mounted. If not, it's not. No stale state possible. +#[cfg(target_os = "linux")] +pub fn get_mounts() -> Vec { + let Ok(contents) = std::fs::read_to_string("/proc/mounts") else { + return vec![]; + }; + contents + .lines() + .filter_map(|line| { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 2 && parts[0].starts_with("agentfs:") { + let agent_id = parts[0].strip_prefix("agentfs:")?.to_string(); + // Skip the internal "fuse" mount used by the daemon + if agent_id == "fuse" { + return None; + } + Some(Mount { + id: agent_id, + mountpoint: PathBuf::from(parts[1]), + }) + } else { + None + } + }) + .collect() +} + +/// Get all currently mounted agentfs filesystems (non-Linux stub) +#[cfg(not(target_os = "linux"))] +pub fn get_mounts() -> Vec { + // On macOS, we could parse the output of `mount` command + // For now, return empty - can be implemented later + vec![] +} diff --git a/crates/agentfs-core/src/options.rs b/crates/agentfs-core/src/options.rs new file mode 100644 index 00000000..e4571b1c --- /dev/null +++ b/crates/agentfs-core/src/options.rs @@ -0,0 +1,217 @@ +use std::path::{Path, PathBuf}; + +use turso::sync::PartialSyncOpts; + +use crate::config::CoreConfig; +use crate::error::{Error, Result}; + +/// Directory containing agentfs databases +pub fn agentfs_dir() -> &'static std::path::Path { + std::path::Path::new(".agentfs") +} + +/// Configuration options for sync +#[derive(Debug, Clone, Default)] +pub struct SyncOptions { + /// Remote URL for syncing + pub remote_url: Option, + /// Auth token for remote sync + pub auth_token: Option, + /// Partial sync options + pub partial_sync: Option, +} + +/// Configuration options for local encryption +#[derive(Debug, Clone)] +pub struct EncryptionConfig { + /// Hex-encoded encryption key + pub hex_key: String, + /// Cipher algorithm (e.g., "aegis256", "aegis128l", "aes256gcm" etc) + pub cipher: String, +} + +/// Configuration options for opening an AgentFS instance +#[derive(Debug, Clone, Default)] +pub struct AgentFSOptions { + /// Optional unique identifier for the agent. + /// - If Some(id): Creates persistent storage at `.agentfs/{id}.db` + /// - If None: Uses ephemeral in-memory database + pub id: Option, + /// Optional custom path to the database file. + /// Takes precedence over `id` if both are set. + pub path: Option, + /// Optional base directory for overlay filesystem (copy-on-write). + /// When set, the filesystem operates as an overlay on top of this directory. + pub base: Option, + /// Sync options for remote database synchronization + pub sync: SyncOptions, + /// Encryption configuration for database at rest + pub encryption: Option, + /// Typed core runtime configuration. When omitted, [`CoreConfig::from_env`] + /// is evaluated once by [`AgentFS::open`](crate::AgentFS::open). + pub core_config: Option, +} + +impl AgentFSOptions { + /// Validates an agent ID to prevent path traversal and ensure safe filesystem operations. + /// Returns true if the ID contains only alphanumeric characters, hyphens, and underscores. + pub fn validate_agent_id(id: &str) -> bool { + !id.is_empty() + && id + .chars() + .all(|c| c.is_alphanumeric() || c == '-' || c == '_') + } + pub fn db_path(&self) -> Result { + // Determine database path: path takes precedence over id + let path = if let Some(path) = &self.path { + // Custom path provided directly + if path == ":memory:" { + return Ok(path.clone()); + } + PathBuf::from(path) + } else if let Some(id) = &self.id { + // Validate agent ID to prevent path traversal attacks + if !Self::validate_agent_id(id) { + return Err(Error::InvalidAgentId(id.clone())); + } + + // Ensure .agentfs directory exists + let agentfs_dir = agentfs_dir(); + if !agentfs_dir.exists() { + std::fs::create_dir_all(agentfs_dir)?; + } + agentfs_dir.join(format!("{}.db", id)) + } else { + // No id or path = ephemeral in-memory database + return Ok(":memory:".to_string()); + }; + // turso retains this string verbatim for by-path operations, and mount + // teardown chdirs the process to `/`; hand it an absolute path so + // nothing downstream can resolve it against the wrong cwd. + let path = std::path::absolute(path)?; + path.into_os_string() + .into_string() + .map_err(|p| Error::InvalidUtf8Path(PathBuf::from(p).display().to_string())) + } + /// Create options for a persistent agent with the given ID + pub fn with_id(id: impl Into) -> Self { + Self { + id: Some(id.into()), + path: None, + base: None, + sync: SyncOptions::default(), + encryption: None, + core_config: None, + } + } + + /// Create options for an ephemeral in-memory agent + pub fn ephemeral() -> Self { + Self { + id: None, + path: None, + base: None, + sync: SyncOptions::default(), + encryption: None, + core_config: None, + } + } + + /// Create options with a custom database path + pub fn with_path(path: impl Into) -> Self { + Self { + id: None, + path: Some(path.into()), + base: None, + sync: SyncOptions::default(), + encryption: None, + core_config: None, + } + } + + /// Set sync options + pub fn with_sync(mut self, sync: SyncOptions) -> Self { + self.sync = sync; + self + } + + /// Set typed core runtime configuration. + pub fn with_core_config(mut self, core_config: CoreConfig) -> Self { + self.core_config = Some(core_config); + self + } + + /// Set the base directory for overlay filesystem (copy-on-write) + pub fn with_base(mut self, base: impl Into) -> Self { + self.base = Some(base.into()); + self + } + + /// Enable local encryption with a hex-encoded key and cipher + /// + /// # Arguments + /// * `hex_key` - Hex-encoded encryption key (64 chars for 256-bit ciphers, 32 for 128-bit) + /// * `cipher` - Cipher algorithm (e.g., "aegis256", "aes256gcm" etc) + pub fn with_encryption_key(mut self, hex_key: &str, cipher: &str) -> Self { + self.encryption = Some(EncryptionConfig { + hex_key: hex_key.to_string(), + cipher: cipher.to_string(), + }); + self + } + + /// Set encryption configuration directly + pub fn with_encryption(mut self, encryption: EncryptionConfig) -> Self { + self.encryption = Some(encryption); + self + } + + /// Resolve an id-or-path string to AgentFSOptions + /// + /// Resolution order (first match wins): + /// 1. `:memory:` -> ephemeral in-memory database + /// 2. Valid agent ID with existing `.agentfs/{id}.db` -> uses that agent + /// 3. Existing file path -> uses that path directly + /// + /// When nothing matches: ID-shaped arguments report `AgentNotFound`, + /// path-shaped arguments (a separator or an extension) report + /// `DatabaseNotFound`, and everything else `InvalidAgentId`. + pub fn resolve(id_or_path: impl Into) -> Result { + let id_or_path = id_or_path.into(); + + if id_or_path == ":memory:" { + return Ok(Self::ephemeral()); + } + + // First, check if it's a valid agent ID with an existing database in .agentfs/ + if AgentFSOptions::validate_agent_id(&id_or_path) { + let db_path = agentfs_dir().join(format!("{}.db", id_or_path)); + if db_path.exists() { + return Ok(Self::with_path(db_path.to_str().ok_or_else(|| { + Error::InvalidUtf8Path(db_path.display().to_string()) + })?)); + } + } + + // Fall back to treating as a direct file path + let path = Path::new(&id_or_path); + if path.is_file() { + Ok(Self::with_path(id_or_path)) + } else if AgentFSOptions::validate_agent_id(&id_or_path) { + // Not a valid agent and not an existing file + Err(Error::AgentNotFound { + id: id_or_path.clone(), + path: agentfs_dir() + .join(format!("{}.db", id_or_path)) + .display() + .to_string(), + }) + } else if id_or_path.contains(std::path::MAIN_SEPARATOR) || path.extension().is_some() { + // Path-shaped (has a separator or an extension) but nothing exists + // there: an "invalid agent ID" complaint would be misleading. + Err(Error::DatabaseNotFound(id_or_path)) + } else { + Err(Error::InvalidAgentId(id_or_path)) + } + } +} diff --git a/sdk/rust/src/connection_pool.rs b/crates/agentfs-core/src/pool.rs similarity index 69% rename from sdk/rust/src/connection_pool.rs rename to crates/agentfs-core/src/pool.rs index 0cdb4a5a..f1545968 100644 --- a/sdk/rust/src/connection_pool.rs +++ b/crates/agentfs-core/src/pool.rs @@ -4,10 +4,7 @@ //! connections with a maximum limit. When the pool is exhausted, callers block //! until a connection becomes available or timeout occurs. -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; +use std::{sync::Arc, time::Duration}; use tokio::sync::{Mutex, OwnedSemaphorePermit, Semaphore}; use turso::{Connection, Database}; @@ -21,7 +18,7 @@ const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30); /// Configuration for a connection pool. #[derive(Clone, Debug)] -pub struct ConnectionPoolOptions { +pub struct PoolOptions { /// Maximum number of connections that may be checked out concurrently. pub max_connections: usize, /// Timeout for acquiring a connection when the pool is exhausted. @@ -30,7 +27,7 @@ pub struct ConnectionPoolOptions { pub setup_sql: Vec, } -impl Default for ConnectionPoolOptions { +impl Default for PoolOptions { fn default() -> Self { Self { max_connections: DEFAULT_MAX_CONNECTIONS, @@ -40,7 +37,7 @@ impl Default for ConnectionPoolOptions { } } -impl ConnectionPoolOptions { +impl PoolOptions { /// Options for a strictly serialized single-connection pool. pub fn single_connection() -> Self { Self { @@ -67,8 +64,10 @@ impl ConnectionPoolOptions { } /// Database wrapper that supports both regular and sync databases. -enum DatabaseType { +pub enum DatabaseType { + /// A local Turso database. Local(Database), + /// A Turso sync database. Sync(turso::sync::Database), } @@ -95,50 +94,8 @@ struct ConnectionPoolInner { } impl ConnectionPool { - /// Create a new conservative single-connection pool from a database. - /// - /// Use `with_options` when a caller knows the database is file-backed and - /// can safely use multiple connections. This default preserves `:memory:` - /// database semantics for standalone subsystem constructors. - pub fn new(db: Database) -> Self { - Self::new_single_connection(db) - } - - /// Create a new single-connection pool from a database. - pub fn new_single_connection(db: Database) -> Self { - Self::with_database_type( - DatabaseType::Local(db), - ConnectionPoolOptions::single_connection(), - ) - } - - /// Create a connection pool with explicit options. - pub fn with_options(db: Database, options: ConnectionPoolOptions) -> Self { - Self::with_database_type(DatabaseType::Local(db), options) - } - - /// Create a new connection pool from a sync database. - pub fn new_sync(db: turso::sync::Database) -> Self { - Self::with_database_type( - DatabaseType::Sync(db), - ConnectionPoolOptions::single_connection(), - ) - } - - /// Create a new single-connection pool from a sync database. - pub fn new_sync_single_connection(db: turso::sync::Database) -> Self { - Self::with_database_type( - DatabaseType::Sync(db), - ConnectionPoolOptions::single_connection(), - ) - } - - /// Create a sync connection pool with explicit options. - pub fn with_sync_options(db: turso::sync::Database, options: ConnectionPoolOptions) -> Self { - Self::with_database_type(DatabaseType::Sync(db), options) - } - - fn with_database_type(db: DatabaseType, options: ConnectionPoolOptions) -> Self { + /// Create a connection pool with explicit database type and options. + pub fn with_options(db: DatabaseType, options: PoolOptions) -> Self { Self { inner: Arc::new(ConnectionPoolInner { db, @@ -163,21 +120,17 @@ impl ConnectionPool { /// available within the timeout period. pub async fn get_connection(&self) -> Result { // Try to acquire a permit with timeout - let wait_started = if crate::profiling::is_enabled() { - Some(Instant::now()) - } else { - None + let permit = { + let _wait_timer = + crate::telemetry::timer(&crate::telemetry::CORE_COUNTERS.connection_wait); + tokio::time::timeout( + self.inner.timeout, + Arc::clone(&self.inner.semaphore).acquire_owned(), + ) + .await + .map_err(|_| Error::ConnectionPoolTimeout)? + .map_err(|_| Error::Internal("semaphore closed".to_string()))? }; - let permit = tokio::time::timeout( - self.inner.timeout, - Arc::clone(&self.inner.semaphore).acquire_owned(), - ) - .await - .map_err(|_| Error::ConnectionPoolTimeout)? - .map_err(|_| Error::Internal("semaphore closed".to_string()))?; - if let Some(wait_started) = wait_started { - crate::profiling::record_connection_wait(wait_started.elapsed()); - } // We have a permit - try to get an existing connection or create new one let conn = { @@ -187,12 +140,12 @@ impl ConnectionPool { let conn = match conn { Some(c) => { - crate::profiling::record_connection_reuse(); + crate::telemetry::record_connection_reuse(); c } None => { let conn = self.create_connection().await?; - crate::profiling::record_connection_create(); + crate::telemetry::record_connection_create(); conn } }; @@ -200,6 +153,7 @@ impl ConnectionPool { Ok(PooledConnection { conn: Some(conn), pool: self.inner.clone(), + discard_on_drop: false, _permit: permit, }) } @@ -243,6 +197,7 @@ impl ConnectionPool { pub struct PooledConnection { conn: Option, pool: Arc, + discard_on_drop: bool, /// Held permit - released when this is dropped _permit: OwnedSemaphorePermit, } @@ -252,6 +207,35 @@ impl PooledConnection { pub fn connection(&self) -> &Connection { self.conn.as_ref().expect("connection already taken") } + + /// Mark this connection as unhealthy so it is evicted instead of reused. + /// + /// Callers that observe a fatal database error can keep the pool from + /// handing the same connection to the next borrower. The semaphore permit + /// is still released normally when the pooled wrapper drops. + pub fn mark_unhealthy(&mut self) { + self.discard_on_drop = true; + } + + /// Mark this connection unhealthy when `error` indicates the connection + /// should not be returned to the reusable pool. + pub fn mark_unhealthy_if_fatal(&mut self, error: &Error) { + if is_fatal_connection_error(error) { + self.mark_unhealthy(); + } + } +} + +pub(crate) fn is_fatal_connection_error(error: &Error) -> bool { + matches!( + error, + Error::Database( + turso::Error::Corrupt(_) + | turso::Error::IoError(_, _) + | turso::Error::Misuse(_) + | turso::Error::NotAdb(_) + ) + ) } impl std::ops::Deref for PooledConnection { @@ -265,10 +249,16 @@ impl std::ops::Deref for PooledConnection { impl Drop for PooledConnection { fn drop(&mut self) { if let Some(conn) = self.conn.take() { + if self.discard_on_drop { + crate::telemetry::record_connection_health_eviction(); + return; + } // Return connection to pool - use try_lock to avoid blocking in drop // If we can't get the lock, just drop the connection (it will be recreated) if let Ok(mut pool) = self.pool.pool.try_lock() { pool.push(conn); + } else { + crate::telemetry::record_connection_drop_discard(); } // Permit is automatically released when _permit is dropped } @@ -284,7 +274,8 @@ mod tests { #[tokio::test] async fn test_connection_pool_basic() { let db = Builder::new_local(":memory:").build().await.unwrap(); - let pool = ConnectionPool::new(db); + let pool = + ConnectionPool::with_options(DatabaseType::Local(db), PoolOptions::single_connection()); // Get a connection let conn = pool.get_connection().await.unwrap(); @@ -301,7 +292,8 @@ mod tests { #[tokio::test] async fn test_default_pool_is_single_connection() { let db = Builder::new_local(":memory:").build().await.unwrap(); - let pool = ConnectionPool::new(db); + let pool = + ConnectionPool::with_options(DatabaseType::Local(db), PoolOptions::single_connection()); let conn1 = pool.get_connection().await.unwrap(); let pool_clone = pool.clone(); @@ -317,8 +309,8 @@ mod tests { async fn test_single_connection_pool_times_out_under_contention() { let db = Builder::new_local(":memory:").build().await.unwrap(); let pool = ConnectionPool::with_options( - db, - ConnectionPoolOptions::single_connection().with_timeout(Duration::from_millis(50)), + DatabaseType::Local(db), + PoolOptions::single_connection().with_timeout(Duration::from_millis(50)), ); // Get the one allowed connection @@ -340,8 +332,8 @@ mod tests { // Create pool with very short timeout let db = Builder::new_local(":memory:").build().await.unwrap(); let pool = ConnectionPool::with_options( - db, - ConnectionPoolOptions::single_connection().with_timeout(Duration::from_millis(50)), + DatabaseType::Local(db), + PoolOptions::single_connection().with_timeout(Duration::from_millis(50)), ); // Hold the one connection @@ -355,7 +347,8 @@ mod tests { #[tokio::test] async fn test_connection_pool_concurrent_waiters() { let db = Builder::new_local(":memory:").build().await.unwrap(); - let pool = ConnectionPool::new_single_connection(db); + let pool = + ConnectionPool::with_options(DatabaseType::Local(db), PoolOptions::single_connection()); let counter = Arc::new(AtomicUsize::new(0)); // Spawn multiple tasks that all want the connection @@ -380,6 +373,61 @@ mod tests { assert_eq!(counter.load(Ordering::SeqCst), 5); } + #[tokio::test] + async fn test_drop_discard_is_counted_when_pool_lock_is_busy() { + let db = Builder::new_local(":memory:").build().await.unwrap(); + let pool = + ConnectionPool::with_options(DatabaseType::Local(db), PoolOptions::single_connection()); + + let conn = pool.get_connection().await.unwrap(); + let before = crate::telemetry::snapshot().counter("connection_drop_discards"); + let guard = pool.inner.pool.lock().await; + drop(conn); + drop(guard); + + let after = crate::telemetry::snapshot().counter("connection_drop_discards"); + assert!( + after > before, + "drop-discard counter should increase by at least one: before={before}, after={after}" + ); + } + + #[tokio::test] + async fn test_unhealthy_connection_is_evicted_and_counted() { + let db = Builder::new_local(":memory:").build().await.unwrap(); + let pool = + ConnectionPool::with_options(DatabaseType::Local(db), PoolOptions::single_connection()); + + let mut conn = pool.get_connection().await.unwrap(); + let before = crate::telemetry::snapshot().counter("connection_health_evictions"); + conn.mark_unhealthy(); + drop(conn); + + let after = crate::telemetry::snapshot().counter("connection_health_evictions"); + assert!( + after > before, + "health-eviction counter should increase by at least one: before={before}, after={after}" + ); + assert_eq!(pool.inner.pool.lock().await.len(), 0); + } + + #[test] + fn fatal_connection_error_classifier_is_narrow() { + assert!(is_fatal_connection_error(&Error::Database( + turso::Error::Corrupt("bad page".to_string()) + ))); + assert!(is_fatal_connection_error(&Error::Database( + turso::Error::NotAdb("not an agentfs db".to_string()) + ))); + assert!(is_fatal_connection_error(&Error::Database( + turso::Error::IoError(std::io::ErrorKind::UnexpectedEof, "read") + ))); + assert!(!is_fatal_connection_error(&Error::Database( + turso::Error::Busy("database is busy".to_string()) + ))); + assert!(!is_fatal_connection_error(&Error::ConnectionPoolTimeout)); + } + #[tokio::test] async fn test_file_backed_pool_allows_multiple_connections() { let dir = tempfile::tempdir().unwrap(); @@ -389,10 +437,10 @@ mod tests { .await .unwrap(); let pool = ConnectionPool::with_options( - db, - ConnectionPoolOptions { + DatabaseType::Local(db), + PoolOptions { max_connections: 2, - ..ConnectionPoolOptions::default() + ..PoolOptions::default() }, ); @@ -427,10 +475,10 @@ mod tests { .await .unwrap(); let pool = ConnectionPool::with_options( - db, - ConnectionPoolOptions { + DatabaseType::Local(db), + PoolOptions { max_connections: 2, - ..ConnectionPoolOptions::default().with_setup_sql(["PRAGMA busy_timeout = 1234"]) + ..PoolOptions::default().with_setup_sql(["PRAGMA busy_timeout = 1234"]) }, ); diff --git a/crates/agentfs-core/src/schema/integrity.rs b/crates/agentfs-core/src/schema/integrity.rs new file mode 100644 index 00000000..a2f36609 --- /dev/null +++ b/crates/agentfs-core/src/schema/integrity.rs @@ -0,0 +1,920 @@ +//! Read-only integrity checks for AgentFS databases. + +use crate::error::{Error, Result}; +use serde::Serialize; +use std::collections::BTreeMap; +use std::fs; +use std::path::{Component, Path, PathBuf}; +use turso::{Connection, Value}; + +const S_IFMT: i64 = 0o170000; +const S_IFREG: i64 = 0o100000; +const S_IFDIR: i64 = 0o040000; +const S_IFLNK: i64 = 0o120000; + +#[derive(Debug, Clone)] +struct PartialOriginRow { + base_path: String, + base_fingerprint_size: i64, + base_mtime: i64, + base_mtime_nsec: i64, + base_ctime: i64, + base_ctime_nsec: i64, +} + +/// Options controlling read-only integrity checks. +#[derive(Debug, Clone)] +pub struct CheckOpts { + pub database: PathBuf, + pub require_portable: bool, + pub check_base: bool, +} + +impl CheckOpts { + pub fn new(database: impl Into) -> Self { + Self { + database: database.into(), + require_portable: false, + check_base: false, + } + } + + pub fn require_portable(mut self, require_portable: bool) -> Self { + self.require_portable = require_portable; + self + } + + pub fn check_base(mut self, check_base: bool) -> Self { + self.check_base = check_base; + self + } +} + +/// Integrity report emitted by the CLI and usable by SDK callers. +#[derive(Debug, Serialize)] +pub struct Report { + pub database: String, + pub ok: bool, + pub portable: bool, + pub origin_backed: bool, + pub partial_origin_rows: i64, + pub checks: Vec, +} + +#[derive(Debug, Serialize)] +pub struct Check { + pub name: String, + pub ok: bool, + pub detail: String, + pub violating_rows: Option, +} + +impl Report { + fn new(database: &Path) -> Self { + Self { + database: database.display().to_string(), + ok: true, + portable: true, + origin_backed: false, + partial_origin_rows: 0, + checks: Vec::new(), + } + } + + fn push_check( + &mut self, + name: impl Into, + ok: bool, + detail: impl Into, + violating_rows: Option, + ) { + self.ok &= ok; + self.checks.push(Check { + name: name.into(), + ok, + detail: detail.into(), + violating_rows, + }); + } +} + +/// Run the integrity battery without mutating the database family. +pub async fn check(conn: &Connection, opts: &CheckOpts) -> Result { + let mut report = Report::new(&opts.database); + + let integrity_rows = query_string_column(conn, "PRAGMA integrity_check").await?; + report.push_check( + "pragma.integrity_check", + integrity_rows == ["ok".to_string()], + if integrity_rows == ["ok".to_string()] { + "ok".to_string() + } else { + format!("{integrity_rows:?}") + }, + None, + ); + + let required_tables = [ + "fs_config", + "fs_inode", + "fs_dentry", + "fs_data", + "fs_symlink", + "kv_store", + "tool_calls", + ]; + let mut tables = BTreeMap::new(); + for table in required_tables { + let exists = table_exists(conn, table).await?; + tables.insert(table.to_string(), exists); + report.push_check( + format!("schema.table.{table}"), + exists, + if exists { "present" } else { "missing" }, + if exists { Some(0) } else { Some(1) }, + ); + } + + if *tables.get("fs_config").unwrap_or(&false) { + check_config(conn, &mut report).await?; + } + + let has_inode = *tables.get("fs_inode").unwrap_or(&false); + let has_dentry = *tables.get("fs_dentry").unwrap_or(&false); + let has_data = *tables.get("fs_data").unwrap_or(&false); + let has_symlink = *tables.get("fs_symlink").unwrap_or(&false); + if has_inode && has_data { + check_storage_invariants(conn, &mut report).await?; + } + if has_inode && has_dentry { + check_namespace_invariants(conn, &mut report).await?; + } + if has_inode && has_symlink { + check_symlink_invariants(conn, &mut report).await?; + } + check_portability_status(conn, &mut report, opts.require_portable).await?; + check_optional_overlay_invariants(conn, &mut report, opts.check_base).await?; + + Ok(report) +} + +async fn check_config(conn: &Connection, report: &mut Report) -> Result<()> { + let schema_version = config_string(conn, "schema_version").await?; + report.push_check( + "config.schema_version", + schema_version.as_deref() == Some(super::AGENTFS_SCHEMA_VERSION), + schema_version + .as_deref() + .map(|value| format!("found {value}")) + .unwrap_or_else(|| "missing".to_string()), + if schema_version.as_deref() == Some(super::AGENTFS_SCHEMA_VERSION) { + Some(0) + } else { + Some(1) + }, + ); + + let chunk_size = config_i64(conn, "chunk_size").await?; + report.push_check( + "config.chunk_size", + chunk_size.is_some_and(|value| value > 0), + chunk_size + .map(|value| format!("found {value}")) + .unwrap_or_else(|| "missing or invalid".to_string()), + if chunk_size.is_some_and(|value| value > 0) { + Some(0) + } else { + Some(1) + }, + ); + + let inline_threshold = config_i64(conn, "inline_threshold").await?; + let inline_ok = match (inline_threshold, chunk_size) { + (Some(inline), Some(chunk)) => inline >= 0 && inline <= chunk, + _ => false, + }; + report.push_check( + "config.inline_threshold", + inline_ok, + inline_threshold + .map(|value| format!("found {value}")) + .unwrap_or_else(|| "missing or invalid".to_string()), + if inline_ok { Some(0) } else { Some(1) }, + ); + + Ok(()) +} + +async fn check_storage_invariants(conn: &Connection, report: &mut Report) -> Result<()> { + add_zero_count_check( + conn, + report, + "storage.kind_valid", + "SELECT COUNT(*) FROM fs_inode WHERE storage_kind NOT IN (0, 1)", + ) + .await?; + add_zero_count_check( + conn, + report, + "storage.inline_has_no_chunks", + "SELECT COUNT(*) + FROM fs_inode i + WHERE i.storage_kind = 1 + AND EXISTS (SELECT 1 FROM fs_data d WHERE d.ino = i.ino)", + ) + .await?; + add_zero_count_check( + conn, + report, + "storage.chunked_has_no_inline_data", + "SELECT COUNT(*) FROM fs_inode WHERE storage_kind = 0 AND data_inline IS NOT NULL", + ) + .await?; + add_zero_count_check( + conn, + report, + "storage.inline_size_matches_blob", + "SELECT COUNT(*) + FROM fs_inode + WHERE storage_kind = 1 + AND (data_inline IS NULL OR COALESCE(length(data_inline), 0) != size)", + ) + .await?; + add_zero_count_check( + conn, + report, + "storage.inline_only_regular_files", + &format!( + "SELECT COUNT(*) FROM fs_inode WHERE storage_kind = 1 AND (mode & {S_IFMT}) != {S_IFREG}" + ), + ) + .await?; + add_zero_count_check( + conn, + report, + "storage.non_regular_has_no_inline_data", + &format!( + "SELECT COUNT(*) FROM fs_inode WHERE (mode & {S_IFMT}) != {S_IFREG} AND data_inline IS NOT NULL" + ), + ) + .await?; + add_zero_count_check( + conn, + report, + "storage.chunks_reference_inodes", + "SELECT COUNT(*) + FROM fs_data d + LEFT JOIN fs_inode i ON i.ino = d.ino + WHERE i.ino IS NULL", + ) + .await?; + add_zero_count_check( + conn, + report, + "storage.chunks_nonnegative_index", + "SELECT COUNT(*) FROM fs_data WHERE chunk_index < 0", + ) + .await?; + + if let Some(chunk_size) = config_i64(conn, "chunk_size").await? { + if chunk_size > 0 { + add_zero_count_check( + conn, + report, + "storage.chunk_length_within_chunk_size", + &format!("SELECT COUNT(*) FROM fs_data WHERE length(data) > {chunk_size}"), + ) + .await?; + } + } + add_zero_count_check( + conn, + report, + "storage.chunks_only_regular_files", + &format!( + "SELECT COUNT(*) + FROM fs_data d + JOIN fs_inode i ON i.ino = d.ino + WHERE (i.mode & {S_IFMT}) != {S_IFREG}" + ), + ) + .await?; + + Ok(()) +} + +async fn check_namespace_invariants(conn: &Connection, report: &mut Report) -> Result<()> { + add_exact_count_check( + conn, + report, + "namespace.root_inode", + &format!("SELECT COUNT(*) FROM fs_inode WHERE ino = 1 AND (mode & {S_IFMT}) = {S_IFDIR}"), + 1, + ) + .await?; + add_zero_count_check( + conn, + report, + "namespace.dentry_parent_exists", + "SELECT COUNT(*) + FROM fs_dentry d + LEFT JOIN fs_inode p ON p.ino = d.parent_ino + WHERE p.ino IS NULL", + ) + .await?; + add_zero_count_check( + conn, + report, + "namespace.dentry_parent_is_directory", + &format!( + "SELECT COUNT(*) + FROM fs_dentry d + JOIN fs_inode p ON p.ino = d.parent_ino + WHERE (p.mode & {S_IFMT}) != {S_IFDIR}" + ), + ) + .await?; + add_zero_count_check( + conn, + report, + "namespace.dentry_target_exists", + "SELECT COUNT(*) + FROM fs_dentry d + LEFT JOIN fs_inode i ON i.ino = d.ino + WHERE i.ino IS NULL", + ) + .await?; + add_zero_count_check( + conn, + report, + "namespace.non_root_inode_has_dentry", + "SELECT COUNT(*) + FROM fs_inode i + WHERE i.ino != 1 + AND i.nlink != 0 + AND NOT EXISTS (SELECT 1 FROM fs_dentry d WHERE d.ino = i.ino)", + ) + .await?; + add_zero_count_check( + conn, + report, + "namespace.dentry_names_valid", + "SELECT COUNT(*) + FROM fs_dentry + WHERE name = '' OR name = '.' OR name = '..' OR instr(name, '/') > 0", + ) + .await?; + add_zero_count_check( + conn, + report, + "namespace.non_directory_nlink_matches_dentries", + &format!( + "SELECT COUNT(*) + FROM fs_inode i + WHERE (i.mode & {S_IFMT}) != {S_IFDIR} + AND i.nlink != (SELECT COUNT(*) FROM fs_dentry d WHERE d.ino = i.ino)" + ), + ) + .await?; + add_zero_count_check( + conn, + report, + "namespace.directory_nlink_positive", + &format!("SELECT COUNT(*) FROM fs_inode WHERE (mode & {S_IFMT}) = {S_IFDIR} AND nlink < 1"), + ) + .await?; + + Ok(()) +} + +async fn check_symlink_invariants(conn: &Connection, report: &mut Report) -> Result<()> { + add_zero_count_check( + conn, + report, + "symlink.rows_reference_symlink_inodes", + &format!( + "SELECT COUNT(*) + FROM fs_symlink s + LEFT JOIN fs_inode i ON i.ino = s.ino + WHERE i.ino IS NULL OR (i.mode & {S_IFMT}) != {S_IFLNK}" + ), + ) + .await?; + add_zero_count_check( + conn, + report, + "symlink.inodes_have_rows", + &format!( + "SELECT COUNT(*) + FROM fs_inode i + WHERE (i.mode & {S_IFMT}) = {S_IFLNK} + AND NOT EXISTS (SELECT 1 FROM fs_symlink s WHERE s.ino = i.ino)" + ), + ) + .await +} + +async fn check_portability_status( + conn: &Connection, + report: &mut Report, + require_portable: bool, +) -> Result<()> { + let partial_origin_rows = if table_exists(conn, "fs_partial_origin").await? { + scalar_i64(conn, "SELECT COUNT(*) FROM fs_partial_origin").await? + } else { + 0 + }; + + report.partial_origin_rows = partial_origin_rows; + report.origin_backed = partial_origin_rows > 0; + report.portable = partial_origin_rows == 0; + + report.push_check( + "overlay.portability_status", + true, + if report.portable { + "portable: no partial-origin rows".to_string() + } else { + format!("origin-backed: {partial_origin_rows} partial-origin row(s)") + }, + Some(partial_origin_rows), + ); + + if require_portable { + report.push_check( + "overlay.require_portable", + report.portable, + if report.portable { + "portable requirement satisfied".to_string() + } else { + format!("portable requirement failed: {partial_origin_rows} partial-origin row(s)") + }, + Some(partial_origin_rows), + ); + } + + Ok(()) +} + +async fn check_optional_overlay_invariants( + conn: &Connection, + report: &mut Report, + check_base: bool, +) -> Result<()> { + if table_exists(conn, "fs_origin").await? { + add_zero_count_check( + conn, + report, + "overlay.origin_delta_inode_exists", + "SELECT COUNT(*) + FROM fs_origin o + LEFT JOIN fs_inode i ON i.ino = o.delta_ino + WHERE i.ino IS NULL", + ) + .await?; + } + + if table_exists(conn, "fs_partial_origin").await? { + add_zero_count_check( + conn, + report, + "overlay.partial_origin_delta_inode_exists", + "SELECT COUNT(*) + FROM fs_partial_origin p + LEFT JOIN fs_inode i ON i.ino = p.delta_ino + WHERE i.ino IS NULL", + ) + .await?; + add_zero_count_check( + conn, + report, + "overlay.partial_origin_delta_inode_regular", + &format!( + "SELECT COUNT(*) + FROM fs_partial_origin p + LEFT JOIN fs_inode i ON i.ino = p.delta_ino + WHERE i.ino IS NULL OR (i.mode & {S_IFMT}) != {S_IFREG}" + ), + ) + .await?; + add_zero_count_check( + conn, + report, + "overlay.partial_origin_sizes_valid", + "SELECT COUNT(*) + FROM fs_partial_origin + WHERE base_size < 0 OR base_fingerprint_size < -1", + ) + .await?; + add_zero_count_check( + conn, + report, + "overlay.partial_origin_paths_absolute", + "SELECT COUNT(*) + FROM fs_partial_origin + WHERE base_path = '' OR base_path NOT LIKE '/%' OR instr(base_path, '/../') > 0 OR base_path LIKE '%/..'", + ) + .await?; + if check_base { + check_partial_origin_base_fingerprints(conn, report).await?; + } + } + + if table_exists(conn, "fs_chunk_override").await? { + add_zero_count_check( + conn, + report, + "overlay.chunk_override_delta_inode_exists", + "SELECT COUNT(*) + FROM fs_chunk_override c + LEFT JOIN fs_inode i ON i.ino = c.delta_ino + WHERE i.ino IS NULL", + ) + .await?; + add_zero_count_check( + conn, + report, + "overlay.chunk_override_nonnegative_index", + "SELECT COUNT(*) FROM fs_chunk_override WHERE chunk_index < 0", + ) + .await?; + if table_exists(conn, "fs_partial_origin").await? { + add_zero_count_check( + conn, + report, + "overlay.chunk_override_references_partial_origin", + "SELECT COUNT(*) + FROM fs_chunk_override c + LEFT JOIN fs_partial_origin p ON p.delta_ino = c.delta_ino + WHERE p.delta_ino IS NULL", + ) + .await?; + } else { + add_zero_count_check( + conn, + report, + "overlay.chunk_override_requires_partial_origin_table", + "SELECT COUNT(*) FROM fs_chunk_override", + ) + .await?; + } + add_zero_count_check( + conn, + report, + "overlay.chunk_override_unique", + "SELECT COUNT(*) + FROM ( + SELECT delta_ino, chunk_index, COUNT(*) AS n + FROM fs_chunk_override + GROUP BY delta_ino, chunk_index + HAVING n > 1 + )", + ) + .await?; + if let Some(chunk_size) = config_i64(conn, "chunk_size").await? { + if chunk_size > 0 { + add_zero_count_check( + conn, + report, + "overlay.chunk_override_index_in_range", + &format!( + "SELECT COUNT(*) + FROM fs_chunk_override c + JOIN fs_inode i ON i.ino = c.delta_ino + WHERE c.chunk_index * {chunk_size} >= i.size" + ), + ) + .await?; + } + } + } + + if table_exists(conn, "fs_whiteout").await? { + add_zero_count_check( + conn, + report, + "overlay.whiteout_paths_absolute", + "SELECT COUNT(*) + FROM fs_whiteout + WHERE path NOT LIKE '/%' OR parent_path NOT LIKE '/%'", + ) + .await?; + } + + Ok(()) +} + +async fn check_partial_origin_base_fingerprints( + conn: &Connection, + report: &mut Report, +) -> Result<()> { + let partial_rows = load_partial_origin_rows(conn).await?; + if partial_rows.is_empty() { + report.push_check( + "overlay.partial_origin_base_fingerprints", + true, + "no partial-origin rows".to_string(), + Some(0), + ); + return Ok(()); + } + + let base_root = match read_overlay_base_root(conn).await { + Ok(root) => root, + Err(err) => { + report.push_check( + "overlay.partial_origin_base_fingerprints", + false, + err.to_string(), + Some(partial_rows.len() as i64), + ); + return Ok(()); + } + }; + + let mut violations = 0; + let mut first_error = None; + for partial in &partial_rows { + let result = (|| -> Result<()> { + let base_path = resolve_materialization_base_path(&base_root, &partial.base_path)?; + let metadata = fs::metadata(&base_path).map_err(|err| { + Error::Internal(format!( + "Failed to stat base file {}: {err}", + base_path.display() + )) + })?; + validate_base_fingerprint(partial, &metadata, &base_path) + })(); + if let Err(err) = result { + violations += 1; + if first_error.is_none() { + first_error = Some(err.to_string()); + } + } + } + + report.push_check( + "overlay.partial_origin_base_fingerprints", + violations == 0, + if violations == 0 { + format!("{} base fingerprint(s) valid", partial_rows.len()) + } else { + format!( + "{violations} base fingerprint violation(s); first: {}", + first_error.unwrap_or_else(|| "unknown".to_string()) + ) + }, + Some(violations), + ); + Ok(()) +} + +async fn load_partial_origin_rows(conn: &Connection) -> Result> { + if !table_exists(conn, "fs_partial_origin").await? { + return Ok(Vec::new()); + } + + let mut rows = conn + .query( + "SELECT base_path, base_size, base_fingerprint_size, + base_mtime, base_mtime_nsec, base_ctime, base_ctime_nsec + FROM fs_partial_origin + ORDER BY delta_ino", + (), + ) + .await?; + let mut partial_rows = Vec::new(); + while let Some(row) = rows.next().await? { + let base_path: String = row.get(0)?; + let base_size = value_i64(row.get_value(1)?)?; + let raw_fingerprint_size = value_i64(row.get_value(2)?)?; + partial_rows.push(PartialOriginRow { + base_path, + base_fingerprint_size: if raw_fingerprint_size < 0 { + base_size + } else { + raw_fingerprint_size + }, + base_mtime: value_i64(row.get_value(3)?)?, + base_mtime_nsec: value_i64(row.get_value(4)?)?, + base_ctime: value_i64(row.get_value(5)?)?, + base_ctime_nsec: value_i64(row.get_value(6)?)?, + }); + } + Ok(partial_rows) +} + +async fn read_overlay_base_root(conn: &Connection) -> Result { + if !table_exists(conn, "fs_overlay_config").await? { + return Err(Error::Internal( + "partial-origin database is missing fs_overlay_config".to_string(), + )); + } + let mut rows = conn + .query( + "SELECT value FROM fs_overlay_config WHERE key = 'base_path'", + (), + ) + .await?; + let row = rows.next().await?.ok_or_else(|| { + Error::Internal( + "partial-origin database is missing fs_overlay_config.base_path".to_string(), + ) + })?; + let base_path: String = row.get(0)?; + let base_root = PathBuf::from(base_path); + base_root.canonicalize().map_err(|err| { + Error::Internal(format!( + "Failed to canonicalize base root {}: {err}", + base_root.display() + )) + }) +} + +fn resolve_materialization_base_path(base_root: &Path, recorded_path: &str) -> Result { + let mut candidate = base_root.to_path_buf(); + for component in Path::new(recorded_path).components() { + match component { + Component::RootDir | Component::CurDir => {} + Component::Normal(part) => candidate.push(part), + Component::ParentDir => { + return Err(Error::Internal(format!( + "partial-origin base path escapes base root: {recorded_path}" + ))) + } + Component::Prefix(_) => { + return Err(Error::Internal(format!( + "partial-origin base path has an unsupported prefix: {recorded_path}" + ))) + } + } + } + + let canonical = candidate.canonicalize().map_err(|err| { + Error::Internal(format!( + "Failed to canonicalize base path {}: {err}", + candidate.display() + )) + })?; + if !canonical.starts_with(base_root) { + return Err(Error::Internal(format!( + "partial-origin base path escapes base root: {}", + canonical.display() + ))); + } + Ok(canonical) +} + +fn validate_base_fingerprint( + partial: &PartialOriginRow, + metadata: &fs::Metadata, + path: &Path, +) -> Result<()> { + let fingerprint = metadata_fingerprint(metadata); + if fingerprint.size != partial.base_fingerprint_size + || fingerprint.mtime != partial.base_mtime + || fingerprint.mtime_nsec != partial.base_mtime_nsec + || fingerprint.ctime != partial.base_ctime + || fingerprint.ctime_nsec != partial.base_ctime_nsec + { + return Err(Error::Internal(format!( + "partial-origin base changed for {} (stored size={}, current size={}, path={})", + partial.base_path, + partial.base_fingerprint_size, + fingerprint.size, + path.display() + ))); + } + Ok(()) +} + +struct FileFingerprint { + size: i64, + mtime: i64, + mtime_nsec: i64, + ctime: i64, + ctime_nsec: i64, +} + +#[cfg(unix)] +fn metadata_fingerprint(metadata: &fs::Metadata) -> FileFingerprint { + use std::os::unix::fs::MetadataExt; + FileFingerprint { + size: metadata.len() as i64, + mtime: metadata.mtime(), + mtime_nsec: metadata.mtime_nsec(), + ctime: metadata.ctime(), + ctime_nsec: metadata.ctime_nsec(), + } +} + +#[cfg(not(unix))] +fn metadata_fingerprint(metadata: &fs::Metadata) -> FileFingerprint { + let modified = metadata + .modified() + .ok() + .and_then(|time| time.duration_since(std::time::UNIX_EPOCH).ok()) + .map(|duration| (duration.as_secs() as i64, duration.subsec_nanos() as i64)) + .unwrap_or((0, 0)); + FileFingerprint { + size: metadata.len() as i64, + mtime: modified.0, + mtime_nsec: modified.1, + ctime: 0, + ctime_nsec: 0, + } +} + +async fn add_zero_count_check( + conn: &Connection, + report: &mut Report, + name: &str, + sql: &str, +) -> Result<()> { + let count = scalar_i64(conn, sql).await?; + report.push_check( + name, + count == 0, + if count == 0 { + "0 violating rows".to_string() + } else { + format!("{count} violating rows") + }, + Some(count), + ); + Ok(()) +} + +async fn add_exact_count_check( + conn: &Connection, + report: &mut Report, + name: &str, + sql: &str, + expected: i64, +) -> Result<()> { + let count = scalar_i64(conn, sql).await?; + report.push_check( + name, + count == expected, + format!("found {count}, expected {expected}"), + if count == expected { + Some(0) + } else { + Some((count - expected).abs()) + }, + ); + Ok(()) +} + +async fn table_exists(conn: &Connection, table: &str) -> Result { + let mut rows = conn + .query( + "SELECT name FROM sqlite_master WHERE type='table' AND name=?", + (table,), + ) + .await?; + Ok(rows.next().await?.is_some()) +} + +async fn query_string_column(conn: &Connection, sql: &str) -> Result> { + let mut rows = conn.query(sql, ()).await?; + let mut values = Vec::new(); + while let Some(row) = rows.next().await? { + values.push(row.get(0)?); + } + Ok(values) +} + +async fn scalar_i64(conn: &Connection, sql: &str) -> Result { + let mut rows = conn.query(sql, ()).await?; + let row = rows + .next() + .await? + .ok_or_else(|| Error::Internal(format!("query returned no rows: {sql}")))?; + value_i64(row.get_value(0)?) +} + +async fn config_string(conn: &Connection, key: &str) -> Result> { + let mut rows = conn + .query("SELECT value FROM fs_config WHERE key = ?", (key,)) + .await?; + if let Some(row) = rows.next().await? { + Ok(Some(row.get(0)?)) + } else { + Ok(None) + } +} + +async fn config_i64(conn: &Connection, key: &str) -> Result> { + let Some(value) = config_string(conn, key).await? else { + return Ok(None); + }; + Ok(value.parse::().ok()) +} + +fn value_i64(value: Value) -> Result { + value + .as_integer() + .copied() + .ok_or_else(|| Error::Internal("Expected integer result".to_string())) +} diff --git a/crates/agentfs-core/src/schema/mod.rs b/crates/agentfs-core/src/schema/mod.rs new file mode 100644 index 00000000..c6c0f592 --- /dev/null +++ b/crates/agentfs-core/src/schema/mod.rs @@ -0,0 +1,1412 @@ +//! Schema authority for AgentFS databases. +//! +//! This module owns all production Rust DDL, schema-version detection, and +//! user_version keyed migrations for the pre-crate-split SDK core. + +pub mod integrity; + +use crate::config::{DEFAULT_CHUNK_SIZE, DEFAULT_INLINE_THRESHOLD}; +use crate::error::{Error, Result}; +use turso::transaction::{Transaction, TransactionBehavior}; +use turso::{Connection, Value}; + +/// Current schema version. +pub const CURRENT: SchemaVersion = SchemaVersion::V0_5; + +/// Compatibility string for callers that still surface the historical version. +pub const AGENTFS_SCHEMA_VERSION: &str = CURRENT.as_str(); +pub const CONFIG_SCHEMA_VERSION_KEY: &str = "schema_version"; +pub const CONFIG_CHUNK_SIZE_KEY: &str = "chunk_size"; +pub const CONFIG_INLINE_THRESHOLD_KEY: &str = "inline_threshold"; + +/// Detected schema version based on PRAGMA user_version, with fs_config and +/// column-sniffing compatibility for pre-user_version databases. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum SchemaVersion { + /// Base schema: fs_inode, fs_dentry, fs_data, fs_symlink, fs_config, kv_store, tool_calls + V0_0, + /// Added nlink column to fs_inode + V0_2, + /// Added atime_nsec, mtime_nsec, ctime_nsec, rdev columns to fs_inode + V0_4, + /// Added inline small-file storage columns and overlay sidecar tables + V0_5, +} + +impl std::fmt::Display for SchemaVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +impl SchemaVersion { + /// Returns the version string. + pub const fn as_str(self) -> &'static str { + match self { + SchemaVersion::V0_0 => "0.0", + SchemaVersion::V0_2 => "0.2", + SchemaVersion::V0_4 => "0.4", + SchemaVersion::V0_5 => "0.5", + } + } + + /// Returns the PRAGMA user_version value for this schema. + pub const fn user_version(self) -> i64 { + match self { + SchemaVersion::V0_0 => 0, + SchemaVersion::V0_2 => 2, + SchemaVersion::V0_4 => 4, + SchemaVersion::V0_5 => 5, + } + } + + /// Returns true if this version is the current version. + pub const fn is_current(self) -> bool { + matches!(self, CURRENT) + } + + /// Parse a version marker string (e.g. "0.4") into a known schema version. + pub fn parse(marker: &str) -> Option { + match marker { + "0.0" => Some(SchemaVersion::V0_0), + "0.2" => Some(SchemaVersion::V0_2), + "0.4" => Some(SchemaVersion::V0_4), + "0.5" => Some(SchemaVersion::V0_5), + _ => None, + } + } + + fn from_user_version(version: i64) -> Option { + match version { + 0 => Some(SchemaVersion::V0_0), + 2 => Some(SchemaVersion::V0_2), + 4 => Some(SchemaVersion::V0_4), + 5 => Some(SchemaVersion::V0_5), + _ => None, + } + } +} + +/// A single ordered migration keyed by SQLite PRAGMA user_version. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Migration { + pub from: SchemaVersion, + pub to: SchemaVersion, + pub description: &'static str, +} + +const MIGRATIONS: &[Migration] = &[ + Migration { + from: SchemaVersion::V0_0, + to: SchemaVersion::V0_2, + description: "add fs_inode.nlink", + }, + Migration { + from: SchemaVersion::V0_2, + to: SchemaVersion::V0_4, + description: "add nanosecond timestamps and rdev", + }, + Migration { + from: SchemaVersion::V0_4, + to: SchemaVersion::V0_5, + description: "add inline storage and overlay schema sections", + }, +]; + +/// Ordered migrations to the current schema. +pub fn migrations() -> &'static [Migration] { + MIGRATIONS +} + +/// Migrations that would be applied from `from` to [`CURRENT`]. +pub fn pending_migrations(from: SchemaVersion) -> Vec<&'static Migration> { + let mut version = from; + let mut pending = Vec::new(); + while version != CURRENT { + let Some(migration) = MIGRATIONS + .iter() + .find(|migration| migration.from == version) + else { + break; + }; + pending.push(migration); + version = migration.to; + } + pending +} + +/// Single production DDL source. +pub mod ddl { + use super::SchemaVersion; + + /// Returns all DDL statements needed for the requested schema version. + pub fn create_all(_version: SchemaVersion) -> &'static [&'static str] { + CURRENT_DDL + } + + const CURRENT_DDL: &[&str] = &[ + "CREATE TABLE IF NOT EXISTS fs_config ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + )", + "CREATE TABLE IF NOT EXISTS fs_inode ( + ino INTEGER PRIMARY KEY AUTOINCREMENT, + mode INTEGER NOT NULL, + nlink INTEGER NOT NULL DEFAULT 0, + uid INTEGER NOT NULL DEFAULT 0, + gid INTEGER NOT NULL DEFAULT 0, + size INTEGER NOT NULL DEFAULT 0, + atime INTEGER NOT NULL, + mtime INTEGER NOT NULL, + ctime INTEGER NOT NULL, + rdev INTEGER NOT NULL DEFAULT 0, + atime_nsec INTEGER NOT NULL DEFAULT 0, + mtime_nsec INTEGER NOT NULL DEFAULT 0, + ctime_nsec INTEGER NOT NULL DEFAULT 0, + data_inline BLOB, + storage_kind INTEGER NOT NULL DEFAULT 0 + )", + "CREATE TABLE IF NOT EXISTS fs_dentry ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parent_ino INTEGER NOT NULL, + ino INTEGER NOT NULL, + UNIQUE(parent_ino, name) + )", + "CREATE INDEX IF NOT EXISTS idx_fs_dentry_parent ON fs_dentry(parent_ino, name)", + "CREATE INDEX IF NOT EXISTS idx_fs_dentry_parent_ino ON fs_dentry(parent_ino, ino)", + "CREATE TABLE IF NOT EXISTS fs_data ( + ino INTEGER NOT NULL, + chunk_index INTEGER NOT NULL, + data BLOB NOT NULL, + PRIMARY KEY (ino, chunk_index) + )", + "CREATE TABLE IF NOT EXISTS fs_symlink ( + ino INTEGER PRIMARY KEY, + target TEXT NOT NULL + )", + "CREATE TABLE IF NOT EXISTS fs_whiteout ( + path TEXT PRIMARY KEY, + parent_path TEXT NOT NULL, + created_at INTEGER NOT NULL + )", + "CREATE INDEX IF NOT EXISTS idx_fs_whiteout_parent ON fs_whiteout(parent_path)", + "CREATE TABLE IF NOT EXISTS fs_overlay_config ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + )", + "CREATE TABLE IF NOT EXISTS fs_origin ( + delta_ino INTEGER PRIMARY KEY, + base_ino INTEGER NOT NULL + )", + "CREATE TABLE IF NOT EXISTS fs_partial_origin ( + delta_ino INTEGER PRIMARY KEY, + base_ino INTEGER NOT NULL, + base_path TEXT NOT NULL, + base_size INTEGER NOT NULL, + base_fingerprint_size INTEGER NOT NULL DEFAULT -1, + base_mtime INTEGER NOT NULL DEFAULT 0, + base_mtime_nsec INTEGER NOT NULL DEFAULT 0, + base_ctime INTEGER NOT NULL DEFAULT 0, + base_ctime_nsec INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL + )", + "CREATE TABLE IF NOT EXISTS fs_chunk_override ( + delta_ino INTEGER NOT NULL, + chunk_index INTEGER NOT NULL, + PRIMARY KEY (delta_ino, chunk_index) + )", + "CREATE TABLE IF NOT EXISTS kv_store ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + created_at INTEGER DEFAULT (unixepoch()), + updated_at INTEGER DEFAULT (unixepoch()) + )", + "CREATE INDEX IF NOT EXISTS idx_kv_store_created_at ON kv_store(created_at)", + "CREATE TABLE IF NOT EXISTS tool_calls ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parameters TEXT, + result TEXT, + error TEXT, + status TEXT NOT NULL DEFAULT 'pending', + started_at INTEGER NOT NULL, + completed_at INTEGER, + duration_ms INTEGER + )", + "CREATE INDEX IF NOT EXISTS idx_tool_calls_name ON tool_calls(name)", + "CREATE INDEX IF NOT EXISTS idx_tool_calls_started_at ON tool_calls(started_at)", + ]; +} + +#[derive(Debug)] +struct ColumnInfo { + name: String, + type_name: String, + not_null: bool, + default_value: Option, +} + +#[derive(Clone, Copy)] +struct ColumnSpec { + table_name: &'static str, + column_name: &'static str, + type_name: &'static str, + not_null: bool, + default_value: Option<&'static str>, +} + +const CURRENT_COLUMN_SPECS: &[ColumnSpec] = &[ + ColumnSpec { + table_name: "fs_inode", + column_name: "nlink", + type_name: "INTEGER", + not_null: true, + default_value: Some("0"), + }, + ColumnSpec { + table_name: "fs_inode", + column_name: "atime_nsec", + type_name: "INTEGER", + not_null: true, + default_value: Some("0"), + }, + ColumnSpec { + table_name: "fs_inode", + column_name: "mtime_nsec", + type_name: "INTEGER", + not_null: true, + default_value: Some("0"), + }, + ColumnSpec { + table_name: "fs_inode", + column_name: "ctime_nsec", + type_name: "INTEGER", + not_null: true, + default_value: Some("0"), + }, + ColumnSpec { + table_name: "fs_inode", + column_name: "rdev", + type_name: "INTEGER", + not_null: true, + default_value: Some("0"), + }, + ColumnSpec { + table_name: "fs_inode", + column_name: "data_inline", + type_name: "BLOB", + not_null: false, + default_value: None, + }, + ColumnSpec { + table_name: "fs_inode", + column_name: "storage_kind", + type_name: "INTEGER", + not_null: true, + default_value: Some("0"), + }, +]; + +const REQUIRED_CURRENT_TABLES: &[&str] = &[ + "fs_config", + "fs_inode", + "fs_dentry", + "fs_data", + "fs_symlink", + "fs_whiteout", + "fs_overlay_config", + "fs_origin", + "fs_partial_origin", + "fs_chunk_override", + "kv_store", + "tool_calls", +]; + +/// Detect the schema version of an existing database. +/// +/// Returns `None` if the database has no `fs_inode` table and is therefore a +/// new database from the schema authority's perspective. +pub async fn detect_schema_version(conn: &Connection) -> Result> { + let raw_user_version = user_version(conn).await?; + if raw_user_version > 0 { + let version = SchemaVersion::from_user_version(raw_user_version).ok_or_else(|| { + Error::SchemaVersionMismatch { + found: format!("user_version {raw_user_version}"), + expected: CURRENT.to_string(), + } + })?; + return Ok(Some(version)); + } + + if !table_exists(conn, "fs_inode").await? { + return Ok(None); + } + + let columns = get_table_columns(conn, "fs_inode").await?; + let has_nlink = columns.iter().any(|c| c.name == "nlink"); + let has_atime_nsec = columns.iter().any(|c| c.name == "atime_nsec"); + let has_mtime_nsec = columns.iter().any(|c| c.name == "mtime_nsec"); + let has_ctime_nsec = columns.iter().any(|c| c.name == "ctime_nsec"); + let has_rdev = columns.iter().any(|c| c.name == "rdev"); + let has_data_inline = columns.iter().any(|c| c.name == "data_inline"); + let has_storage_kind = columns.iter().any(|c| c.name == "storage_kind"); + + // Pre-user_version v0.5 databases are recognized by columns. The old + // fs_config markers are compatibility hints, not authoritative identity. + if has_data_inline && has_storage_kind { + return Ok(Some(SchemaVersion::V0_5)); + } + + if has_atime_nsec && has_mtime_nsec && has_ctime_nsec && has_rdev { + return Ok(Some(SchemaVersion::V0_4)); + } + + if has_nlink { + return Ok(Some(SchemaVersion::V0_2)); + } + + Ok(Some(SchemaVersion::V0_0)) +} + +/// Check that a database has the current schema version. +/// +/// This is a read-only check. Opening paths should call [`ensure_current`] so +/// pending migrations run instead of performing unversioned implicit changes. +pub async fn check_schema_version(conn: &Connection) -> Result<()> { + if let Some(version) = detect_schema_version(conn).await? { + if !version.is_current() { + return Err(Error::SchemaVersionMismatch { + found: version.to_string(), + expected: CURRENT.to_string(), + }); + } + validate_current_schema(conn).await?; + } + Ok(()) +} + +/// Gate for open paths: create fresh databases and normalize already-current +/// ones (compat columns, missing indexes, `user_version` stamp), but never run +/// version upgrades. An older supported schema returns +/// [`Error::SchemaVersionMismatch`] so callers can direct the user to +/// `agentfs migrate`, which owns explicit upgrades via [`ensure_current`]. +pub async fn require_current(conn: &Connection) -> Result<()> { + if let Some(version) = detect_schema_version(conn).await? { + if version != CURRENT { + return Err(Error::SchemaVersionMismatch { + found: version.to_string(), + expected: CURRENT.to_string(), + }); + } + } + ensure_current(conn).await +} + +/// Ensure the database is at [`CURRENT`], running all pending migrations inside +/// a single IMMEDIATE transaction and stamping `PRAGMA user_version` before the +/// DDL transaction commits. +pub async fn ensure_current(conn: &Connection) -> Result<()> { + let raw_user_version = user_version(conn).await?; + let detected = detect_schema_version(conn).await?; + + if raw_user_version == CURRENT.user_version() { + validate_current_schema(conn).await?; + ensure_current_indexes(conn).await?; + return Ok(()); + } + + let txn = Transaction::new_unchecked(conn, TransactionBehavior::Immediate).await?; + let result = async { + // Legacy overlay sidecar tables can predate columns that CURRENT_DDL + // indexes. Repair and backfill those columns before running any + // dependent CREATE INDEX statements from the single DDL list. + ensure_overlay_compat_columns(conn).await?; + execute_current_ddl(conn).await?; + if let Some(version) = detected { + apply_pending_migrations(conn, version).await?; + } + ensure_config_defaults(conn).await?; + set_user_version(conn, CURRENT).await?; + Ok(()) + } + .await; + + match result { + Ok(()) => txn.commit().await?, + Err(err) => { + let _ = txn.rollback().await; + return Err(err); + } + } + + validate_current_schema(conn).await?; + Ok(()) +} + +/// Set or update the overlay base-path marker without owning any DDL locally. +pub async fn set_overlay_base_path(conn: &Connection, base_path: &str) -> Result<()> { + ensure_current(conn).await?; + conn.execute( + "INSERT OR REPLACE INTO fs_overlay_config (key, value) VALUES ('base_path', ?1)", + [Value::Text(base_path.to_string())], + ) + .await?; + Ok(()) +} + +async fn apply_pending_migrations(conn: &Connection, from: SchemaVersion) -> Result<()> { + let mut version = from; + while version != CURRENT { + let Some(migration) = MIGRATIONS + .iter() + .find(|migration| migration.from == version) + else { + return Err(Error::SchemaVersionMismatch { + found: version.to_string(), + expected: CURRENT.to_string(), + }); + }; + apply_migration(conn, migration).await?; + set_user_version(conn, migration.to).await?; + version = migration.to; + } + Ok(()) +} + +async fn apply_migration(conn: &Connection, migration: &Migration) -> Result<()> { + match (migration.from, migration.to) { + (SchemaVersion::V0_0, SchemaVersion::V0_2) => { + add_column_idempotent( + conn, + ColumnSpec { + table_name: "fs_inode", + column_name: "nlink", + type_name: "INTEGER", + not_null: true, + default_value: Some("0"), + }, + "ALTER TABLE fs_inode ADD COLUMN nlink INTEGER NOT NULL DEFAULT 0", + ) + .await?; + conn.execute( + "UPDATE fs_inode + SET nlink = CASE + WHEN ino = 1 THEN 2 + WHEN (mode & 61440) = 16384 THEN MAX(1, (SELECT COUNT(*) FROM fs_dentry d WHERE d.ino = fs_inode.ino)) + ELSE (SELECT COUNT(*) FROM fs_dentry d WHERE d.ino = fs_inode.ino) + END", + (), + ) + .await?; + Ok(()) + } + (SchemaVersion::V0_2, SchemaVersion::V0_4) => { + add_column_idempotent( + conn, + ColumnSpec { + table_name: "fs_inode", + column_name: "atime_nsec", + type_name: "INTEGER", + not_null: true, + default_value: Some("0"), + }, + "ALTER TABLE fs_inode ADD COLUMN atime_nsec INTEGER NOT NULL DEFAULT 0", + ) + .await?; + add_column_idempotent( + conn, + ColumnSpec { + table_name: "fs_inode", + column_name: "mtime_nsec", + type_name: "INTEGER", + not_null: true, + default_value: Some("0"), + }, + "ALTER TABLE fs_inode ADD COLUMN mtime_nsec INTEGER NOT NULL DEFAULT 0", + ) + .await?; + add_column_idempotent( + conn, + ColumnSpec { + table_name: "fs_inode", + column_name: "ctime_nsec", + type_name: "INTEGER", + not_null: true, + default_value: Some("0"), + }, + "ALTER TABLE fs_inode ADD COLUMN ctime_nsec INTEGER NOT NULL DEFAULT 0", + ) + .await?; + add_column_idempotent( + conn, + ColumnSpec { + table_name: "fs_inode", + column_name: "rdev", + type_name: "INTEGER", + not_null: true, + default_value: Some("0"), + }, + "ALTER TABLE fs_inode ADD COLUMN rdev INTEGER NOT NULL DEFAULT 0", + ) + .await + } + (SchemaVersion::V0_4, SchemaVersion::V0_5) => { + add_column_idempotent( + conn, + ColumnSpec { + table_name: "fs_inode", + column_name: "data_inline", + type_name: "BLOB", + not_null: false, + default_value: None, + }, + "ALTER TABLE fs_inode ADD COLUMN data_inline BLOB", + ) + .await?; + add_column_idempotent( + conn, + ColumnSpec { + table_name: "fs_inode", + column_name: "storage_kind", + type_name: "INTEGER", + not_null: true, + default_value: Some("0"), + }, + "ALTER TABLE fs_inode ADD COLUMN storage_kind INTEGER NOT NULL DEFAULT 0", + ) + .await + } + _ => Err(Error::Internal(format!( + "unsupported schema migration {} -> {}", + migration.from, migration.to + ))), + } +} + +async fn execute_current_ddl(conn: &Connection) -> Result<()> { + for sql in ddl::create_all(CURRENT) { + conn.execute(*sql, ()).await?; + } + Ok(()) +} + +async fn ensure_current_indexes(conn: &Connection) -> Result<()> { + // Indexes are not represented in the column-based current-schema sniffing + // used for legacy DB compatibility, so make newly added planner indexes + // idempotently present when opening an already-current database. + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_fs_dentry_parent ON fs_dentry(parent_ino, name)", + (), + ) + .await?; + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_fs_dentry_parent_ino ON fs_dentry(parent_ino, ino)", + (), + ) + .await?; + Ok(()) +} + +async fn ensure_overlay_compat_columns(conn: &Connection) -> Result<()> { + if table_exists(conn, "fs_partial_origin").await? { + add_column_if_missing( + conn, + "fs_partial_origin", + "base_fingerprint_size", + "ALTER TABLE fs_partial_origin ADD COLUMN base_fingerprint_size INTEGER NOT NULL DEFAULT -1", + ) + .await?; + add_column_if_missing( + conn, + "fs_partial_origin", + "base_mtime", + "ALTER TABLE fs_partial_origin ADD COLUMN base_mtime INTEGER NOT NULL DEFAULT 0", + ) + .await?; + add_column_if_missing( + conn, + "fs_partial_origin", + "base_mtime_nsec", + "ALTER TABLE fs_partial_origin ADD COLUMN base_mtime_nsec INTEGER NOT NULL DEFAULT 0", + ) + .await?; + add_column_if_missing( + conn, + "fs_partial_origin", + "base_ctime", + "ALTER TABLE fs_partial_origin ADD COLUMN base_ctime INTEGER NOT NULL DEFAULT 0", + ) + .await?; + add_column_if_missing( + conn, + "fs_partial_origin", + "base_ctime_nsec", + "ALTER TABLE fs_partial_origin ADD COLUMN base_ctime_nsec INTEGER NOT NULL DEFAULT 0", + ) + .await?; + } + + if table_exists(conn, "fs_whiteout").await? + && add_column_if_missing( + conn, + "fs_whiteout", + "parent_path", + "ALTER TABLE fs_whiteout ADD COLUMN parent_path TEXT NOT NULL DEFAULT '/'", + ) + .await? + { + let mut rows = conn.query("SELECT path FROM fs_whiteout", ()).await?; + let mut paths = Vec::new(); + while let Some(row) = rows.next().await? { + let path: String = row.get(0)?; + paths.push(path); + } + for path in paths { + conn.execute( + "UPDATE fs_whiteout SET parent_path = ? WHERE path = ?", + (parent_path_for_whiteout(&path), path), + ) + .await?; + } + } + + Ok(()) +} + +async fn ensure_config_defaults(conn: &Connection) -> Result<()> { + conn.execute( + "INSERT OR REPLACE INTO fs_config (key, value) VALUES (?, ?)", + (CONFIG_SCHEMA_VERSION_KEY, CURRENT.as_str()), + ) + .await?; + conn.execute( + "INSERT OR IGNORE INTO fs_config (key, value) VALUES (?, ?)", + (CONFIG_CHUNK_SIZE_KEY, DEFAULT_CHUNK_SIZE.to_string()), + ) + .await?; + // Old databases keep their recorded chunk_size (e.g. 4096); a defaulted + // inline_threshold must not exceed it or the storage invariant + // `inline_threshold <= chunk_size` breaks on migrated databases. + let chunk_size = read_config_value(conn, CONFIG_CHUNK_SIZE_KEY) + .await? + .and_then(|value| value.parse::().ok()) + .unwrap_or(DEFAULT_CHUNK_SIZE); + conn.execute( + "INSERT OR IGNORE INTO fs_config (key, value) VALUES (?, ?)", + ( + CONFIG_INLINE_THRESHOLD_KEY, + DEFAULT_INLINE_THRESHOLD.min(chunk_size).to_string(), + ), + ) + .await?; + Ok(()) +} + +async fn read_config_value(conn: &Connection, key: &str) -> Result> { + let mut rows = conn + .query("SELECT value FROM fs_config WHERE key = ?", (key,)) + .await?; + if let Some(row) = rows.next().await? { + Ok(Some(row.get(0)?)) + } else { + Ok(None) + } +} + +async fn add_column_if_missing( + conn: &Connection, + table_name: &str, + column_name: &str, + sql: &str, +) -> Result { + if column_exists(conn, table_name, column_name).await? { + return Ok(false); + } + conn.execute(sql, ()).await?; + Ok(true) +} + +async fn column_exists(conn: &Connection, table_name: &str, column_name: &str) -> Result { + Ok(get_table_columns(conn, table_name) + .await? + .iter() + .any(|column| column.name == column_name)) +} + +fn parent_path_for_whiteout(path: &str) -> String { + if path == "/" { + return "/".to_string(); + } + match path.rsplit_once('/') { + Some(("", _)) | None => "/".to_string(), + Some((parent, _)) => parent.to_string(), + } +} + +async fn validate_current_schema(conn: &Connection) -> Result<()> { + for table in REQUIRED_CURRENT_TABLES { + if !table_exists(conn, table).await? { + return Err(Error::Internal(format!( + "current schema is missing required table {table}" + ))); + } + } + + for spec in CURRENT_COLUMN_SPECS { + ensure_column_matches(conn, *spec).await?; + } + + Ok(()) +} + +async fn user_version(conn: &Connection) -> Result { + let mut rows = conn.query("PRAGMA user_version", ()).await?; + let row = rows + .next() + .await? + .ok_or_else(|| Error::Internal("PRAGMA user_version returned no rows".to_string()))?; + row.get(0).map_err(Error::from) +} + +async fn set_user_version(conn: &Connection, version: SchemaVersion) -> Result<()> { + conn.execute( + &format!("PRAGMA user_version = {}", version.user_version()), + (), + ) + .await?; + Ok(()) +} + +async fn table_exists(conn: &Connection, table_name: &str) -> Result { + let mut rows = conn + .query( + "SELECT name FROM sqlite_master WHERE type='table' AND name=?", + (table_name,), + ) + .await?; + Ok(rows.next().await?.is_some()) +} + +async fn get_table_columns(conn: &Connection, table_name: &str) -> Result> { + let mut rows = conn + .query(&format!("PRAGMA table_info({})", table_name), ()) + .await?; + + let mut columns = Vec::new(); + while let Some(row) = rows.next().await? { + let name: String = row.get(1)?; + let type_name: String = row.get(2)?; + let not_null: i64 = row.get(3)?; + let default_value = match row.get_value(4).ok() { + Some(Value::Text(value)) => Some(value.clone()), + Some(Value::Integer(value)) => Some(value.to_string()), + Some(Value::Null) | None => None, + Some(value) => Some(format!("{value:?}")), + }; + columns.push(ColumnInfo { + name, + type_name, + not_null: not_null != 0, + default_value, + }); + } + + Ok(columns) +} + +async fn add_column_idempotent(conn: &Connection, spec: ColumnSpec, sql: &str) -> Result<()> { + match conn.execute(sql, ()).await { + Ok(_) => Ok(()), + Err(err) if is_duplicate_column_error(&err) => ensure_column_matches(conn, spec).await, + Err(err) => Err(Error::Internal(format!( + "schema ALTER failed while adding {}.{}: {err}", + spec.table_name, spec.column_name + ))), + } +} + +async fn ensure_column_matches(conn: &Connection, spec: ColumnSpec) -> Result<()> { + let columns = get_table_columns(conn, spec.table_name).await?; + for column in columns { + if column.name != spec.column_name { + continue; + } + + let type_matches = column.type_name.eq_ignore_ascii_case(spec.type_name); + let default_matches = column.default_value.as_deref() == spec.default_value; + if type_matches && column.not_null == spec.not_null && default_matches { + return Ok(()); + } + + return Err(Error::Internal(format!( + "schema column {}.{} already exists with incompatible definition: \ + expected type={} not_null={} default={:?}; \ + found type={} not_null={} default={:?}", + spec.table_name, + spec.column_name, + spec.type_name, + spec.not_null, + spec.default_value, + column.type_name, + column.not_null, + column.default_value + ))); + } + + Err(Error::Internal(format!( + "schema column {}.{} is missing", + spec.table_name, spec.column_name + ))) +} + +fn is_duplicate_column_error(err: &turso::Error) -> bool { + err.to_string() + .to_ascii_lowercase() + .contains("duplicate column") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{AgentFS, AgentFSOptions, KvStore, ToolCalls, DEFAULT_FILE_MODE}; + use std::path::{Path, PathBuf}; + use tempfile::tempdir; + use turso::Builder; + + const S_IFDIR: i64 = 0o040000; + const S_IFREG: i64 = 0o100000; + + #[tokio::test] + async fn schema_user_version_migrations_from_all_fixtures() -> Result<()> { + for version in [ + SchemaVersion::V0_0, + SchemaVersion::V0_2, + SchemaVersion::V0_4, + SchemaVersion::V0_5, + ] { + let dir = tempdir()?; + let db_path = dir.path().join(format!("fixture-{}.db", version.as_str())); + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + create_legacy_fixture(&conn, version).await?; + + let kv_before = scalar_i64(&conn, "SELECT COUNT(*) FROM kv_store").await?; + let tool_before = scalar_i64(&conn, "SELECT COUNT(*) FROM tool_calls").await?; + let data_before = read_fixture_file_bytes(&conn).await?; + + ensure_current(&conn).await?; + + assert_eq!(user_version(&conn).await?, CURRENT.user_version()); + assert_eq!(detect_schema_version(&conn).await?, Some(CURRENT)); + assert_eq!( + kv_before, + scalar_i64(&conn, "SELECT COUNT(*) FROM kv_store").await? + ); + assert_eq!( + tool_before, + scalar_i64(&conn, "SELECT COUNT(*) FROM tool_calls").await? + ); + assert_eq!(data_before, read_fixture_file_bytes(&conn).await?); + + drop(conn); + drop(db); + let agent = AgentFS::open(AgentFSOptions::with_path(db_path.to_string_lossy())).await?; + assert_eq!(agent.fs.read_file("/file.txt").await?.unwrap(), b"abcdef"); + let conn = agent.get_connection().await?; + let report = + integrity::check(&conn, &integrity::CheckOpts::new(db_path.clone())).await?; + assert!(report.ok, "integrity failed for migrated {version}"); + } + Ok(()) + } + + #[tokio::test] + async fn migrated_defaults_keep_inline_threshold_within_chunk_size() -> Result<()> { + let dir = tempdir()?; + let db_path = dir.path().join("legacy-small-chunks.db"); + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + create_legacy_fixture(&conn, SchemaVersion::V0_4).await?; + conn.execute( + "DELETE FROM fs_config WHERE key = ?", + (CONFIG_INLINE_THRESHOLD_KEY,), + ) + .await?; + conn.execute( + "UPDATE fs_config SET value = '4096' WHERE key = ?", + (CONFIG_CHUNK_SIZE_KEY,), + ) + .await?; + + ensure_current(&conn).await?; + + let inline_threshold = read_config_value(&conn, CONFIG_INLINE_THRESHOLD_KEY) + .await? + .expect("inline_threshold default must be inserted"); + assert_eq!(inline_threshold, "4096"); + let report = integrity::check(&conn, &integrity::CheckOpts::new(db_path.clone())).await?; + let failing = report + .checks + .iter() + .filter(|check| !check.ok) + .map(|check| check.name.clone()) + .collect::>(); + assert!(report.ok, "integrity failed: {failing:?}"); + Ok(()) + } + + #[tokio::test] + async fn open_paths_reject_old_schema_without_upgrading() -> Result<()> { + for version in [ + SchemaVersion::V0_0, + SchemaVersion::V0_2, + SchemaVersion::V0_4, + ] { + let dir = tempdir()?; + let db_path = dir.path().join(format!("old-{}.db", version.as_str())); + { + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + create_legacy_fixture(&conn, version).await?; + } + + let err = + match AgentFS::open(AgentFSOptions::with_path(db_path.to_string_lossy())).await { + Ok(_) => panic!("{version}: AgentFS::open must not upgrade an old schema"), + Err(err) => err, + }; + assert!( + matches!(err, Error::SchemaVersionMismatch { .. }), + "{version}: unexpected open error {err}" + ); + let kv_err = match KvStore::new(db_path.to_str().unwrap()).await { + Ok(_) => panic!("{version}: KvStore::new must not upgrade an old schema"), + Err(err) => err, + }; + assert!( + matches!(kv_err, Error::SchemaVersionMismatch { .. }), + "{version}: unexpected kv error {kv_err}" + ); + let tool_err = match ToolCalls::new(db_path.to_str().unwrap()).await { + Ok(_) => panic!("{version}: ToolCalls::new must not upgrade an old schema"), + Err(err) => err, + }; + assert!( + matches!(tool_err, Error::SchemaVersionMismatch { .. }), + "{version}: unexpected tool-calls error {tool_err}" + ); + + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + assert_eq!(user_version(&conn).await?, 0, "{version}: db was stamped"); + assert_eq!(detect_schema_version(&conn).await?, Some(version)); + let columns = get_table_columns(&conn, "fs_inode").await?; + assert!( + !columns.iter().any(|column| column.name == "data_inline"), + "{version}: open added v0.5 columns" + ); + + ensure_current(&conn).await?; + drop(conn); + drop(db); + let agent = AgentFS::open(AgentFSOptions::with_path(db_path.to_string_lossy())).await?; + assert_eq!(agent.fs.read_file("/file.txt").await?.unwrap(), b"abcdef"); + } + Ok(()) + } + + #[tokio::test] + async fn legacy_whiteout_without_parent_path_migrates_for_sdk_openers() -> Result<()> { + let dir = tempdir()?; + + let agent_path = + create_legacy_whiteout_fixture_file(dir.path(), "agent-open", SchemaVersion::V0_5) + .await?; + let agent = AgentFS::open(AgentFSOptions::with_path(agent_path.to_string_lossy())).await?; + assert_eq!(agent.fs.read_file("/file.txt").await?.unwrap(), b"abcdef"); + drop(agent); + assert_legacy_whiteout_parent_path(&agent_path, "AgentFS::open").await?; + + let kv_path = + create_legacy_whiteout_fixture_file(dir.path(), "kv-open", SchemaVersion::V0_5).await?; + let kv = KvStore::new(kv_path.to_str().unwrap()).await?; + kv.set("after", &serde_json::json!({ "ok": true })).await?; + drop(kv); + assert_legacy_whiteout_parent_path(&kv_path, "KvStore::new").await?; + + let tool_path = + create_legacy_whiteout_fixture_file(dir.path(), "tool-open", SchemaVersion::V0_5) + .await?; + let tools = ToolCalls::new(tool_path.to_str().unwrap()).await?; + let id = tools.start("after", None).await?; + tools.success(id, None).await?; + drop(tools); + assert_legacy_whiteout_parent_path(&tool_path, "ToolCalls::new").await?; + + Ok(()) + } + + #[tokio::test] + async fn schema_interrupted_init_reopens_or_errors_cleanly() -> Result<()> { + let dir = tempdir()?; + let empty_path = dir.path().join("empty.db"); + let db = Builder::new_local(empty_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + ensure_current(&conn).await?; + assert_eq!(user_version(&conn).await?, CURRENT.user_version()); + + let config_only_path = dir.path().join("config-only.db"); + let db = Builder::new_local(config_only_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + conn.execute( + "CREATE TABLE fs_config (key TEXT PRIMARY KEY, value TEXT NOT NULL)", + (), + ) + .await?; + ensure_current(&conn).await?; + assert_eq!(user_version(&conn).await?, CURRENT.user_version()); + + let hybrid_path = dir.path().join("hybrid-v05-no-markers.db"); + let db = Builder::new_local(hybrid_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + create_legacy_fixture(&conn, SchemaVersion::V0_5).await?; + conn.execute( + "DELETE FROM fs_config WHERE key = ?", + (CONFIG_SCHEMA_VERSION_KEY,), + ) + .await?; + ensure_current(&conn).await?; + assert_eq!(detect_schema_version(&conn).await?, Some(CURRENT)); + let agent = AgentFS::open(AgentFSOptions::with_path(hybrid_path.to_string_lossy())).await?; + assert_eq!(agent.fs.read_file("/file.txt").await?.unwrap(), b"abcdef"); + + let corrupt_current_path = dir.path().join("current-missing-table.db"); + let db = Builder::new_local(corrupt_current_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + set_user_version(&conn, CURRENT).await?; + let err = ensure_current(&conn) + .await + .expect_err("missing current tables must error"); + assert!( + err.to_string() + .contains("current schema is missing required table"), + "unexpected error: {err}" + ); + + Ok(()) + } + + async fn create_legacy_whiteout_fixture_file( + dir: &Path, + name: &str, + version: SchemaVersion, + ) -> Result { + let db_path = dir.join(format!("{name}.db")); + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + create_legacy_fixture(&conn, version).await?; + add_legacy_whiteout_without_parent_path(&conn).await?; + drop(conn); + drop(db); + Ok(db_path) + } + + async fn add_legacy_whiteout_without_parent_path(conn: &Connection) -> Result<()> { + conn.execute( + "CREATE TABLE fs_whiteout ( + path TEXT PRIMARY KEY, + created_at INTEGER NOT NULL + )", + (), + ) + .await?; + conn.execute( + "INSERT INTO fs_whiteout (path, created_at) VALUES + ('/dir/deleted', 123), + ('/top-level', 456)", + (), + ) + .await?; + Ok(()) + } + + async fn assert_legacy_whiteout_parent_path(db_path: &Path, label: &str) -> Result<()> { + let db = Builder::new_local(db_path.to_str().unwrap()) + .build() + .await?; + let conn = db.connect()?; + + let column_names = get_table_columns(&conn, "fs_whiteout") + .await? + .into_iter() + .map(|column| column.name) + .collect::>(); + assert!( + column_names.iter().any(|column| column == "parent_path"), + "{label} did not add fs_whiteout.parent_path; columns={column_names:?}" + ); + + let mut rows = conn + .query( + "SELECT path, parent_path, created_at + FROM fs_whiteout + ORDER BY path", + (), + ) + .await?; + let mut migrated = Vec::new(); + while let Some(row) = rows.next().await? { + migrated.push(( + row.get::(0)?, + row.get::(1)?, + row.get::(2)?, + )); + } + println!("{label}: fs_whiteout columns={column_names:?}; rows={migrated:?}"); + assert_eq!( + migrated, + vec![ + ("/dir/deleted".to_string(), "/dir".to_string(), 123), + ("/top-level".to_string(), "/".to_string(), 456), + ] + ); + assert_eq!(user_version(&conn).await?, CURRENT.user_version()); + Ok(()) + } + + async fn create_legacy_fixture(conn: &Connection, version: SchemaVersion) -> Result<()> { + conn.execute( + "CREATE TABLE fs_config (key TEXT PRIMARY KEY, value TEXT NOT NULL)", + (), + ) + .await?; + conn.execute( + "INSERT INTO fs_config (key, value) VALUES ('chunk_size', '4')", + (), + ) + .await?; + if version == SchemaVersion::V0_5 { + conn.execute( + "INSERT INTO fs_config (key, value) VALUES ('schema_version', '0.5'), ('inline_threshold', '4')", + (), + ) + .await?; + } else { + conn.execute( + "INSERT INTO fs_config (key, value) VALUES ('schema_version', ?), ('inline_threshold', '4')", + (version.as_str(),), + ) + .await?; + } + + let mut columns = vec![ + "ino INTEGER PRIMARY KEY AUTOINCREMENT", + "mode INTEGER NOT NULL", + "uid INTEGER NOT NULL DEFAULT 0", + "gid INTEGER NOT NULL DEFAULT 0", + "size INTEGER NOT NULL DEFAULT 0", + "atime INTEGER NOT NULL", + "mtime INTEGER NOT NULL", + "ctime INTEGER NOT NULL", + ]; + if version >= SchemaVersion::V0_2 { + columns.insert(2, "nlink INTEGER NOT NULL DEFAULT 0"); + } + if version >= SchemaVersion::V0_4 { + columns.extend([ + "rdev INTEGER NOT NULL DEFAULT 0", + "atime_nsec INTEGER NOT NULL DEFAULT 0", + "mtime_nsec INTEGER NOT NULL DEFAULT 0", + "ctime_nsec INTEGER NOT NULL DEFAULT 0", + ]); + } + if version >= SchemaVersion::V0_5 { + columns.extend([ + "data_inline BLOB", + "storage_kind INTEGER NOT NULL DEFAULT 0", + ]); + } + conn.execute( + &format!("CREATE TABLE fs_inode ({})", columns.join(", ")), + (), + ) + .await?; + conn.execute( + "CREATE TABLE fs_dentry ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parent_ino INTEGER NOT NULL, + ino INTEGER NOT NULL, + UNIQUE(parent_ino, name) + )", + (), + ) + .await?; + conn.execute( + "CREATE TABLE fs_data ( + ino INTEGER NOT NULL, + chunk_index INTEGER NOT NULL, + data BLOB NOT NULL, + PRIMARY KEY (ino, chunk_index) + )", + (), + ) + .await?; + conn.execute( + "CREATE TABLE fs_symlink (ino INTEGER PRIMARY KEY, target TEXT NOT NULL)", + (), + ) + .await?; + conn.execute( + "CREATE TABLE kv_store ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + created_at INTEGER DEFAULT (unixepoch()), + updated_at INTEGER DEFAULT (unixepoch()) + )", + (), + ) + .await?; + conn.execute( + "CREATE TABLE tool_calls ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parameters TEXT, + result TEXT, + error TEXT, + status TEXT NOT NULL DEFAULT 'pending', + started_at INTEGER NOT NULL, + completed_at INTEGER, + duration_ms INTEGER + )", + (), + ) + .await?; + + insert_legacy_inode(conn, version, 1, S_IFDIR | 0o755, 2, 0).await?; + insert_legacy_inode(conn, version, 2, S_IFREG | DEFAULT_FILE_MODE as i64, 1, 6).await?; + conn.execute( + "INSERT INTO fs_dentry (name, parent_ino, ino) VALUES ('file.txt', 1, 2)", + (), + ) + .await?; + conn.execute( + "INSERT INTO fs_data (ino, chunk_index, data) VALUES (2, 0, ?), (2, 1, ?)", + (Value::Blob(b"abcd".to_vec()), Value::Blob(b"ef".to_vec())), + ) + .await?; + conn.execute( + "INSERT INTO kv_store (key, value) VALUES ('k', '{\"v\":1}')", + (), + ) + .await?; + conn.execute( + "INSERT INTO tool_calls (name, parameters, status, started_at) VALUES ('tool', '{}', 'success', 1)", + (), + ) + .await?; + Ok(()) + } + + async fn insert_legacy_inode( + conn: &Connection, + version: SchemaVersion, + ino: i64, + mode: i64, + nlink: i64, + size: i64, + ) -> Result<()> { + let mut columns = vec![ + "ino", "mode", "uid", "gid", "size", "atime", "mtime", "ctime", + ]; + let mut values = vec![ + Value::Integer(ino), + Value::Integer(mode), + Value::Integer(0), + Value::Integer(0), + Value::Integer(size), + Value::Integer(1), + Value::Integer(1), + Value::Integer(1), + ]; + if version >= SchemaVersion::V0_2 { + columns.insert(2, "nlink"); + values.insert(2, Value::Integer(nlink)); + } + if version >= SchemaVersion::V0_4 { + columns.extend(["rdev", "atime_nsec", "mtime_nsec", "ctime_nsec"]); + values.extend([ + Value::Integer(0), + Value::Integer(0), + Value::Integer(0), + Value::Integer(0), + ]); + } + if version >= SchemaVersion::V0_5 { + columns.extend(["data_inline", "storage_kind"]); + values.extend([Value::Null, Value::Integer(0)]); + } + let placeholders = std::iter::repeat_n("?", columns.len()) + .collect::>() + .join(", "); + conn.execute( + &format!( + "INSERT INTO fs_inode ({}) VALUES ({})", + columns.join(", "), + placeholders + ), + values, + ) + .await?; + Ok(()) + } + + async fn read_fixture_file_bytes(conn: &Connection) -> Result> { + let mut rows = conn + .query( + "SELECT data FROM fs_data WHERE ino = 2 ORDER BY chunk_index", + (), + ) + .await?; + let mut bytes = Vec::new(); + while let Some(row) = rows.next().await? { + match row.get_value(0)? { + Value::Blob(chunk) => bytes.extend(chunk), + other => { + return Err(Error::Internal(format!( + "unexpected fs_data value in fixture: {other:?}" + ))) + } + } + } + Ok(bytes) + } + + async fn scalar_i64(conn: &Connection, sql: &str) -> Result { + let mut rows = conn.query(sql, ()).await?; + let row = rows + .next() + .await? + .ok_or_else(|| Error::Internal(format!("query returned no rows: {sql}")))?; + row.get(0).map_err(Error::from) + } +} diff --git a/crates/agentfs-core/src/semantics/access.rs b/crates/agentfs-core/src/semantics/access.rs new file mode 100644 index 00000000..345c8851 --- /dev/null +++ b/crates/agentfs-core/src/semantics/access.rs @@ -0,0 +1,434 @@ +//! POSIX access checks shared by every transport adapter. +//! +//! FUSE delegates enforcement to the kernel with `default_permissions`; NFS +//! cannot, so this module is the single server-side implementation of the same +//! owner/group/other, sticky-bit, setattr, and privilege-bit rules. + +use crate::fs::{FsError, Stats}; +use smallvec::SmallVec; + +const S_IRUSR: u32 = 0o400; +const S_IWUSR: u32 = 0o200; +const S_IXUSR: u32 = 0o100; +const S_IRGRP: u32 = 0o040; +const S_IWGRP: u32 = 0o020; +const S_IXGRP: u32 = 0o010; +const S_IROTH: u32 = 0o004; +const S_IWOTH: u32 = 0o002; +const S_IXOTH: u32 = 0o001; +const S_ISUID: u32 = 0o4000; +const S_ISGID: u32 = 0o2000; +const S_ISVTX: u32 = 0o1000; +const S_IXUGO: u32 = S_IXUSR | S_IXGRP | S_IXOTH; +const KILLPRIV_BITS: u32 = S_ISUID | S_ISGID; +const PERMISSION_BITS: u32 = 0o7777; + +/// Caller credentials used for POSIX permission checks. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Credentials { + pub uid: u32, + pub gid: u32, + pub groups: SmallVec<[u32; 16]>, +} + +/// Requested timestamp update in an attribute change. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub enum TimeUpdate { + #[default] + Omit, + Now, + Explicit, +} + +/// Attribute changes whose authorization depends on POSIX permissions. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct AttrChange { + pub size: bool, + pub mode: Option, + pub uid: Option, + pub gid: Option, + pub atime: TimeUpdate, + pub mtime: TimeUpdate, +} + +/// Return whether `creds` may read `stats`. +pub fn may_read(stats: &Stats, creds: &Credentials) -> bool { + is_root(creds) || selected_bits(stats, creds).read +} + +/// Return whether `creds` may write `stats`. +pub fn may_write(stats: &Stats, creds: &Credentials) -> bool { + is_root(creds) || selected_bits(stats, creds).write +} + +/// Return whether `creds` may search a directory or execute a file. +pub fn may_search(stats: &Stats, creds: &Credentials) -> bool { + if is_root(creds) { + return stats.is_directory() || (stats.mode & S_IXUGO) != 0; + } + selected_bits(stats, creds).execute +} + +/// Return whether `creds` may delete or rename `victim` from `parent`. +pub fn sticky_delete_ok(parent: &Stats, victim: &Stats, creds: &Credentials) -> bool { + if !may_modify_directory(parent, creds) { + return false; + } + if (parent.mode & S_ISVTX) == 0 { + return true; + } + is_root(creds) || creds.uid == parent.uid || creds.uid == victim.uid +} + +/// Return whether `creds` may create, remove, or rename entries in `parent`. +pub fn may_modify_directory(parent: &Stats, creds: &Credentials) -> bool { + may_write(parent, creds) && may_search(parent, creds) +} + +/// Return whether `creds` may rename `victim` out of `parent`. +/// +/// This is `sticky_delete_ok` plus the NFS server's POSIX guard for moving a +/// directory across parents: non-root callers must own the directory whose +/// `..` link is being changed. +pub fn sticky_rename_from_ok( + parent: &Stats, + victim: &Stats, + creds: &Credentials, + crosses_parent: bool, +) -> bool { + if !sticky_delete_ok(parent, victim, creds) { + return false; + } + if crosses_parent && victim.is_directory() && !is_root(creds) && !is_owner(victim, creds) { + return false; + } + true +} + +/// Validate whether `creds` may apply `change` to `stats`. +pub fn setattr_allowed( + stats: &Stats, + creds: &Credentials, + change: &AttrChange, +) -> Result<(), FsError> { + if change.size && !may_write(stats, creds) { + return Err(FsError::PermissionDenied); + } + + if change.mode.is_some() && !is_owner_or_root(stats, creds) { + return Err(FsError::OperationNotPermitted); + } + + if let Some(uid) = change.uid { + if !is_root(creds) && uid != stats.uid { + return Err(FsError::OperationNotPermitted); + } + } + + if let Some(gid) = change.gid { + if !is_root(creds) && (!is_owner(stats, creds) || !is_in_group(creds, gid)) { + return Err(FsError::OperationNotPermitted); + } + } + + if (change.atime == TimeUpdate::Explicit || change.mtime == TimeUpdate::Explicit) + && !is_owner_or_root(stats, creds) + { + return Err(FsError::OperationNotPermitted); + } + + if (change.atime == TimeUpdate::Now || change.mtime == TimeUpdate::Now) + && !is_owner_or_root(stats, creds) + && !may_write(stats, creds) + { + return Err(FsError::PermissionDenied); + } + + Ok(()) +} + +/// Mode bits to clear when file contents, size, or ownership change. +pub fn killpriv_mask(stats: &Stats, _creds: &Credentials) -> u32 { + if !stats.is_file() { + return 0; + } + stats.mode & KILLPRIV_BITS +} + +/// Apply POSIX chmod normalization, including SGID clearing for non-members. +pub fn normalize_setattr_mode( + stats: &Stats, + creds: &Credentials, + change: &AttrChange, +) -> Option { + let mut mode = change.mode? & PERMISSION_BITS; + if !is_root(creds) && (mode & S_ISGID) != 0 { + let effective_gid = change.gid.unwrap_or(stats.gid); + if !is_in_group(creds, effective_gid) { + mode &= !S_ISGID; + } + } + Some(mode) +} + +/// Clear privilege bits from `mode` according to [`killpriv_mask`]. +pub fn without_killpriv(stats: &Stats, creds: &Credentials, mode: u32) -> u32 { + mode & !killpriv_mask(stats, creds) +} + +/// Return whether `creds` is root. +pub fn is_root(creds: &Credentials) -> bool { + creds.uid == 0 +} + +/// Return whether `creds` owns `stats`. +pub fn is_owner(stats: &Stats, creds: &Credentials) -> bool { + creds.uid == stats.uid +} + +/// Return whether `creds` owns `stats` or is root. +pub fn is_owner_or_root(stats: &Stats, creds: &Credentials) -> bool { + is_root(creds) || is_owner(stats, creds) +} + +/// Return whether `creds` belongs to `gid`. +pub fn is_in_group(creds: &Credentials, gid: u32) -> bool { + creds.gid == gid || creds.groups.contains(&gid) +} + +#[derive(Clone, Copy)] +struct Rwx { + read: bool, + write: bool, + execute: bool, +} + +fn selected_bits(stats: &Stats, creds: &Credentials) -> Rwx { + let (read, write, execute) = if creds.uid == stats.uid { + (S_IRUSR, S_IWUSR, S_IXUSR) + } else if is_in_group(creds, stats.gid) { + (S_IRGRP, S_IWGRP, S_IXGRP) + } else { + (S_IROTH, S_IWOTH, S_IXOTH) + }; + + Rwx { + read: (stats.mode & read) != 0, + write: (stats.mode & write) != 0, + execute: (stats.mode & execute) != 0, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::fs::{S_IFDIR, S_IFREG}; + fn creds(uid: u32, gid: u32, groups: &[u32]) -> Credentials { + Credentials { + uid, + gid, + groups: SmallVec::from_slice(groups), + } + } + + fn stats(mode: u32, uid: u32, gid: u32) -> Stats { + Stats { + ino: 2, + mode, + nlink: 1, + uid, + gid, + size: 0, + atime: 0, + mtime: 0, + ctime: 0, + atime_nsec: 0, + mtime_nsec: 0, + ctime_nsec: 0, + rdev: 0, + } + } + + #[test] + fn conformance_table_matches_kernel_default_permissions() { + struct Case { + name: &'static str, + stats: Stats, + creds: Credentials, + read: bool, + write: bool, + search: bool, + } + + let cases = [ + Case { + name: "owner read write execute", + stats: stats(S_IFREG | 0o700, 1000, 2000), + creds: creds(1000, 9999, &[]), + read: true, + write: true, + search: true, + }, + Case { + name: "matching primary group", + stats: stats(S_IFREG | 0o070, 3000, 2000), + creds: creds(1000, 2000, &[]), + read: true, + write: true, + search: true, + }, + Case { + name: "matching auxiliary group", + stats: stats(S_IFREG | 0o040, 3000, 2000), + creds: creds(1000, 1000, &[2000]), + read: true, + write: false, + search: false, + }, + Case { + name: "auxiliary group execute only directory", + stats: stats(S_IFDIR | 0o010, 3000, 2000), + creds: creds(1000, 1000, &[2000]), + read: false, + write: false, + search: true, + }, + Case { + name: "primary group write without execute directory", + stats: stats(S_IFDIR | 0o020, 3000, 2000), + creds: creds(1000, 2000, &[]), + read: false, + write: true, + search: false, + }, + Case { + name: "other write only", + stats: stats(S_IFREG | 0o002, 2000, 2000), + creds: creds(1000, 1000, &[]), + read: false, + write: true, + search: false, + }, + Case { + name: "other execute only file", + stats: stats(S_IFREG | 0o001, 2000, 2000), + creds: creds(1000, 1000, &[]), + read: false, + write: false, + search: true, + }, + Case { + name: "root reads and writes mode zero file", + stats: stats(S_IFREG, 1000, 1000), + creds: creds(0, 0, &[]), + read: true, + write: true, + search: false, + }, + Case { + name: "root searches mode zero directory", + stats: stats(S_IFDIR, 1000, 1000), + creds: creds(0, 0, &[]), + read: true, + write: true, + search: true, + }, + ]; + + println!("shared semantics::access conformance cases"); + let mut mismatches = 0usize; + for case in cases { + let got = ( + may_read(&case.stats, &case.creds), + may_write(&case.stats, &case.creds), + may_search(&case.stats, &case.creds), + ); + let expected = (case.read, case.write, case.search); + println!( + "{}: expected=({},{},{}) access=({},{},{})", + case.name, expected.0, expected.1, expected.2, got.0, got.1, got.2 + ); + if got != expected { + mismatches += 1; + } + } + println!("semantics::access conformance mismatches={mismatches}"); + assert_eq!(mismatches, 0); + } + + #[test] + fn sticky_delete_and_killpriv_cases_match_posix() { + let parent = stats(S_IFDIR | 0o1777, 10, 10); + let victim = stats(S_IFREG | 0o644, 20, 20); + + assert!(sticky_delete_ok(&parent, &victim, &creds(10, 10, &[]))); + assert!(sticky_delete_ok(&parent, &victim, &creds(20, 20, &[]))); + assert!(!sticky_delete_ok(&parent, &victim, &creds(30, 30, &[]))); + + let privileged = stats(S_IFREG | 0o6755, 20, 20); + assert_eq!(killpriv_mask(&privileged, &creds(1000, 1000, &[])), 0o6000); + assert_eq!( + killpriv_mask(&privileged, &creds(0, 0, &[])), + 0o6000, + "root chown/truncate follows Linux killpriv behavior for NFS SETATTR" + ); + assert_eq!( + killpriv_mask(&stats(S_IFDIR | 0o6755, 20, 20), &creds(0, 0, &[])), + 0, + "killpriv only applies to regular files" + ); + } + + #[test] + fn setattr_authorization_cases_match_kernel_rules() { + let owned = stats(S_IFREG | 0o644, 1000, 2000); + let other = creds(3000, 3000, &[]); + let owner = creds(1000, 1000, &[2000]); + + assert!(setattr_allowed( + &owned, + &owner, + &AttrChange { + mode: Some(0o600), + ..Default::default() + } + ) + .is_ok()); + assert!(setattr_allowed( + &owned, + &other, + &AttrChange { + mode: Some(0o600), + ..Default::default() + } + ) + .is_err()); + assert!(setattr_allowed( + &owned, + &owner, + &AttrChange { + gid: Some(2000), + ..Default::default() + } + ) + .is_ok()); + assert!(setattr_allowed( + &owned, + &owner, + &AttrChange { + gid: Some(3000), + ..Default::default() + } + ) + .is_err()); + assert!(setattr_allowed( + &owned, + &owner, + &AttrChange { + atime: TimeUpdate::Explicit, + ..Default::default() + } + ) + .is_ok()); + } +} diff --git a/crates/agentfs-core/src/semantics/durability.rs b/crates/agentfs-core/src/semantics/durability.rs new file mode 100644 index 00000000..f8c71a26 --- /dev/null +++ b/crates/agentfs-core/src/semantics/durability.rs @@ -0,0 +1,129 @@ +//! Durability acknowledgements shared by transport adapters. +//! +//! A handler that acknowledges a write must say whether the acknowledgement is +//! volatile or committed. The facade then owns the mechanical contract for +//! turning that declaration into filesystem work. + +use super::handles::{Authority, Handle, HandleTable}; +use crate::error::Result; +use crate::fs::{BoxedFile, FileSystem, Stats}; +use std::sync::Arc; + +/// Durability level attached to a write acknowledgement. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AckDurability { + /// The bytes may still be buffered by an in-memory batcher. + Volatile, + /// The bytes must be committed before the acknowledgement is returned. + Committed, +} + +/// Receipt returned by [`Semantics::write`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct WriteReceipt { + pub count: usize, + pub durability: AckDurability, +} + +/// Shared semantics facade for adapter-visible filesystem contracts. +#[derive(Clone)] +pub struct Semantics { + fs: Arc, + handles: HandleTable, +} + +impl Semantics { + /// Create a semantics facade over the canonical filesystem implementation. + pub fn new(fs: Arc) -> Self { + Self::new_with_handle_table(fs, HandleTable::default()) + } + + pub fn new_with_handle_table(fs: Arc, handles: HandleTable) -> Self { + fs.register_reap_hook(Arc::new(handles.clone())); + Self { fs, handles } + } + + /// Return coherent attributes for `ino`. + /// + /// Filesystem implementations own their pending-write visibility primitive. + /// AgentFS merges batcher pending state into attributes before returning; + /// adapters should use this method for NFS GETATTR and WCC attributes + /// instead of bypassing the shared coherence contract. + pub async fn stat_coherent(&self, ino: i64) -> Result> { + self.fs.getattr(ino).await + } + + /// Write bytes through an already-open file handle with an explicit ack + /// durability declaration. + pub async fn write( + &self, + file: &BoxedFile, + offset: u64, + data: &[u8], + durability: AckDurability, + ) -> Result { + file.pwrite(offset, data).await?; + if matches!(durability, AckDurability::Committed) { + file.fsync().await?; + } + Ok(WriteReceipt { + count: data.len(), + durability, + }) + } + + /// Write bytes through a semantics-owned cached handle. + pub async fn write_handle( + &self, + handle: &Handle, + offset: u64, + data: &[u8], + durability: AckDurability, + ) -> Result { + self.write(handle.file(), offset, data, durability).await + } + + /// Return a cached open handle for `ino` with the requested authority. + pub async fn open_cached(&self, ino: i64, authority: Authority) -> Result { + self.handles.open_cached(&self.fs, ino, authority).await + } + + /// Insert a caller-generated write-authority token. + pub fn try_grant_write_authority_with_token(&self, ino: i64, token: u64) -> bool { + self.handles + .try_grant_write_authority_with_token(ino, token) + } + + /// Check and refresh write authority carried by a token. + pub fn has_write_authority(&self, ino: i64, token: u64) -> bool { + self.handles.has_write_authority(ino, token) + } + + /// Return a live token for READDIRPLUS, if this inode has write authority. + pub fn authority_token_for_ino(&self, ino: i64) -> Option { + self.handles.authority_token_for_ino(ino) + } + + /// Invalidate all cached handles and write-authority tokens for an inode. + pub fn invalidate_handles(&self, ino: i64) { + self.handles.invalidate_ino(ino); + } + + /// Commit pending writes for one inode or for the whole filesystem. + /// + /// `Some(ino)` is the inode-level barrier used by FUSE fsync and NFS COMMIT + /// once that protocol path lands. `None` is the filesystem-wide drain and + /// checkpoint barrier used before clean shutdown/finalize. + pub async fn commit_barrier(&self, ino: Option) -> Result<()> { + match ino { + Some(ino) => self.fs.drain_inode_writes(ino).await, + None => self.fs.drain_all().await, + } + } + + /// Finalize a clean shutdown after the caller has crossed any required + /// commit barrier. + pub async fn finalize(&self) -> Result<()> { + self.fs.finalize().await + } +} diff --git a/crates/agentfs-core/src/semantics/handles.rs b/crates/agentfs-core/src/semantics/handles.rs new file mode 100644 index 00000000..26ac1663 --- /dev/null +++ b/crates/agentfs-core/src/semantics/handles.rs @@ -0,0 +1,394 @@ +//! Handle and authority state shared by protocol adapters. +//! +//! NFS is stateless, but AgentFS deliberately preserves write authority +//! captured by a successful CREATE response so later mode changes do not make +//! already-open client writeback fail. This table is scoped to one +//! [`Semantics`](super::Semantics) facade. +//! +//! Lock order: the single `inner` mutex is a leaf lock; guards cover only +//! in-memory table updates and are dropped before any filesystem `.await` +//! (`clippy::await_holding_lock` is deny-by-workspace). + +use crate::error::Result; +use crate::fs::{agentfs::ReapHook, BoxedFile, FileSystem}; +use async_trait::async_trait; +use lru::LruCache; +use parking_lot::Mutex; +use std::collections::{HashMap, HashSet}; +use std::num::NonZeroUsize; +use std::sync::Arc; +use turso::Connection; + +const DEFAULT_WRITE_TOKEN_CAPACITY: usize = 16_384; +const DEFAULT_OPEN_HANDLE_CAPACITY: usize = 16_384; + +/// Requested cached-handle authority. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Authority { + /// Read-only data access. + Read, + /// Read/write data or truncate access. + Write, +} + +impl Authority { + fn open_flags(self) -> i32 { + match self { + Self::Read => libc::O_RDONLY, + Self::Write => libc::O_RDWR, + } + } +} + +/// A cached open file handle owned by the semantics layer. +#[derive(Clone)] +pub struct Handle { + ino: i64, + authority: Authority, + file: BoxedFile, +} + +impl Handle { + pub fn ino(&self) -> i64 { + self.ino + } + + pub fn authority(&self) -> Authority { + self.authority + } + + pub fn file(&self) -> &BoxedFile { + &self.file + } +} + +#[derive(Clone, Debug)] +struct TokenEntry { + ino: i64, + last_used: u64, +} + +#[derive(Clone)] +struct OpenEntry { + handle: Handle, + write_capable: bool, +} + +struct HandleTableInner { + tokens: LruCache, + tokens_by_ino: HashMap>, + token_clock: u64, + open_handles: LruCache, +} + +impl HandleTableInner { + fn new(token_capacity: usize, open_capacity: usize) -> Self { + Self { + tokens: LruCache::new( + NonZeroUsize::new(token_capacity).expect("token capacity must be non-zero"), + ), + tokens_by_ino: HashMap::new(), + token_clock: 0, + open_handles: LruCache::new( + NonZeroUsize::new(open_capacity).expect("open capacity must be non-zero"), + ), + } + } + + fn next_token_stamp(&mut self) -> u64 { + let stamp = self.token_clock; + self.token_clock = self.token_clock.wrapping_add(1); + stamp + } + + fn remove_token_from_index(&mut self, ino: i64, token: u64) { + if let Some(tokens) = self.tokens_by_ino.get_mut(&ino) { + tokens.remove(&token); + if tokens.is_empty() { + self.tokens_by_ino.remove(&ino); + } + } + } + + fn insert_token(&mut self, ino: i64, token: u64) { + let stamp = self.next_token_stamp(); + if let Some((evicted_token, evicted_entry)) = self.tokens.push( + token, + TokenEntry { + ino, + last_used: stamp, + }, + ) { + self.remove_token_from_index(evicted_entry.ino, evicted_token); + } + self.tokens_by_ino.entry(ino).or_default().insert(token); + } + + fn touch_token(&mut self, token: u64) { + let stamp = self.next_token_stamp(); + if let Some(entry) = self.tokens.get_mut(&token) { + entry.last_used = stamp; + } + } + + fn invalidate_ino(&mut self, ino: i64) { + if let Some(tokens) = self.tokens_by_ino.remove(&ino) { + for token in tokens { + self.tokens.pop(&token); + } + } + + self.open_handles.pop(&ino); + } +} + +/// Per-semantics table of write-authority tokens and cached open handles. +#[derive(Clone)] +pub struct HandleTable { + inner: Arc>, +} + +impl Default for HandleTable { + fn default() -> Self { + Self::with_limits(DEFAULT_WRITE_TOKEN_CAPACITY, DEFAULT_OPEN_HANDLE_CAPACITY) + } +} + +impl HandleTable { + pub fn with_limits(token_capacity: usize, open_capacity: usize) -> Self { + assert!(token_capacity > 0, "token capacity must be non-zero"); + assert!(open_capacity > 0, "open capacity must be non-zero"); + Self { + inner: Arc::new(Mutex::new(HandleTableInner::new( + token_capacity, + open_capacity, + ))), + } + } + + /// Insert a server-generated authority token. + /// + /// Returns `false` when the token already exists in this table so the + /// caller can retry with new randomness without replacing another handle's + /// authority. + pub fn try_grant_write_authority_with_token(&self, ino: i64, token: u64) -> bool { + let mut inner = self.inner.lock(); + if inner.tokens.peek(&token).is_some() { + return false; + } + inner.insert_token(ino, token); + true + } + + pub fn has_write_authority(&self, ino: i64, token: u64) -> bool { + let mut inner = self.inner.lock(); + let has_authority = inner + .tokens + .peek(&token) + .map(|entry| entry.ino == ino) + .unwrap_or(false); + if has_authority { + inner.touch_token(token); + } + has_authority + } + + /// Return a live authority token for `ino`, if one exists, and mark it + /// recently used. READDIRPLUS uses this to refresh client node handles + /// without stripping CREATE-captured write authority. + pub fn authority_token_for_ino(&self, ino: i64) -> Option { + let mut inner = self.inner.lock(); + let token = inner.tokens_by_ino.get(&ino).and_then(|tokens| { + tokens + .iter() + .filter_map(|token| { + inner + .tokens + .peek(token) + .map(|entry| (*token, entry.last_used)) + }) + .max_by_key(|(_, last_used)| *last_used) + .map(|(token, _)| token) + }); + if let Some(token) = token { + inner.touch_token(token); + } + token + } + + /// Open and cache one handle per inode. + /// + /// A write request upgrades a read-resolved entry by replacing the cached + /// file with an `O_RDWR` open. Overlay copy-up happens during that write + /// open, so later reads must reuse the upgraded file rather than a stale + /// base-layer read handle. + pub async fn open_cached( + &self, + fs: &Arc, + ino: i64, + authority: Authority, + ) -> Result { + let needs_write = matches!(authority, Authority::Write); + { + let mut inner = self.inner.lock(); + if let Some(entry) = inner.open_handles.get(&ino).cloned() { + if !needs_write || entry.write_capable { + return Ok(entry.handle); + } + } + } + + let file = fs.open(ino, authority.open_flags()).await?; + let handle = Handle { + ino, + authority, + file, + }; + + let mut inner = self.inner.lock(); + if let Some(entry) = inner.open_handles.get(&ino).cloned() { + if !needs_write || entry.write_capable { + return Ok(entry.handle); + } + } + + inner.open_handles.push( + ino, + OpenEntry { + handle: handle.clone(), + write_capable: needs_write, + }, + ); + Ok(handle) + } + + pub fn invalidate_ino(&self, ino: i64) { + self.inner.lock().invalidate_ino(ino); + } + + #[cfg(test)] + fn grant_write_authority_with_token_for_test(&self, ino: i64, token: u64) -> u64 { + assert!(self.try_grant_write_authority_with_token(ino, token)); + token + } +} + +#[async_trait] +impl ReapHook for HandleTable { + async fn on_reap(&self, _conn: &Connection, ino: i64) -> Result<()> { + self.invalidate_ino(ino); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{AgentFS, AgentFSOptions, FileSystem, Semantics, DEFAULT_FILE_MODE}; + + #[test] + fn write_authority_tokens_evict_least_recently_used_token() { + let table = HandleTable::with_limits(2, 2); + + let first = table.grant_write_authority_with_token_for_test(10, 101); + let second = table.grant_write_authority_with_token_for_test(20, 202); + assert!(table.has_write_authority(10, first)); + assert!(table.has_write_authority(20, second)); + + // Touch the first token so the second token becomes the LRU victim. + assert!(table.has_write_authority(10, first)); + let third = table.grant_write_authority_with_token_for_test(30, 303); + + assert!(table.has_write_authority(10, first)); + assert!(!table.has_write_authority(20, second)); + assert!(table.has_write_authority(30, third)); + } + + #[test] + fn write_authority_tokens_are_invalidated_by_inode() { + let table = HandleTable::with_limits(4, 4); + + let victim = table.grant_write_authority_with_token_for_test(10, 101); + let unrelated = table.grant_write_authority_with_token_for_test(20, 202); + table.invalidate_ino(10); + + assert!(!table.has_write_authority(10, victim)); + assert!(table.has_write_authority(20, unrelated)); + } + + #[test] + fn readdirplus_can_reuse_a_live_write_authority_token() { + let table = HandleTable::with_limits(4, 4); + let token = table.grant_write_authority_with_token_for_test(10, 101); + + assert_eq!(table.authority_token_for_ino(10), Some(token)); + assert!(table.has_write_authority(10, token)); + assert_eq!(table.authority_token_for_ino(20), None); + } + + #[tokio::test] + async fn open_cached_reuses_and_invalidates_handles_by_inode() -> Result<()> { + let agent = AgentFS::open(AgentFSOptions::ephemeral()).await?; + let fs: Arc = Arc::new(agent.fs); + let (stats, _file) = fs + .create_file(1, "cached.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + let table = HandleTable::with_limits(4, 4); + + let first = table.open_cached(&fs, stats.ino, Authority::Read).await?; + let second = table.open_cached(&fs, stats.ino, Authority::Read).await?; + assert!(Arc::ptr_eq(first.file(), second.file())); + + table.invalidate_ino(stats.ino); + let reopened = table.open_cached(&fs, stats.ino, Authority::Read).await?; + assert!(!Arc::ptr_eq(first.file(), reopened.file())); + Ok(()) + } + + #[tokio::test] + async fn open_cached_upgrades_read_handle_on_first_write_open() -> Result<()> { + let agent = AgentFS::open(AgentFSOptions::ephemeral()).await?; + let fs: Arc = Arc::new(agent.fs); + let (stats, _file) = fs + .create_file(1, "upgrade.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + let table = HandleTable::with_limits(4, 4); + + let read = table.open_cached(&fs, stats.ino, Authority::Read).await?; + assert_eq!(read.authority(), Authority::Read); + + let write = table.open_cached(&fs, stats.ino, Authority::Write).await?; + assert_eq!(write.authority(), Authority::Write); + assert!( + !Arc::ptr_eq(read.file(), write.file()), + "write open must replace a previously read-only cached file" + ); + + let reread = table.open_cached(&fs, stats.ino, Authority::Read).await?; + assert!( + Arc::ptr_eq(write.file(), reread.file()), + "reads after a write upgrade must reuse the write-capable file" + ); + Ok(()) + } + + #[tokio::test] + async fn reap_hook_invalidates_authority_tokens() -> Result<()> { + let agent = AgentFS::open(AgentFSOptions::ephemeral()).await?; + let fs: Arc = Arc::new(agent.fs); + let semantics = Semantics::new(fs.clone()); + let (stats, file) = fs + .create_file(1, "reaped.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + drop(file); + + let token = 404; + assert!(semantics.try_grant_write_authority_with_token(stats.ino, token)); + assert!(semantics.has_write_authority(stats.ino, token)); + + fs.unlink(1, "reaped.txt").await?; + + assert!(!semantics.has_write_authority(stats.ino, token)); + Ok(()) + } +} diff --git a/crates/agentfs-core/src/semantics/mod.rs b/crates/agentfs-core/src/semantics/mod.rs new file mode 100644 index 00000000..492919f2 --- /dev/null +++ b/crates/agentfs-core/src/semantics/mod.rs @@ -0,0 +1,114 @@ +//! Shared semantics facade under the transport adapters. +//! +//! M6 fills this module with the permission, durability, and handle-table +//! contracts that sit under the FUSE and NFS adapters. Access control lives +//! here so transport handlers cannot drift on POSIX mode-bit behavior. + +pub mod access; +mod durability; +mod handles; + +pub use durability::{AckDurability, Semantics, WriteReceipt}; +pub use handles::{Authority, Handle, HandleTable}; + +#[cfg(test)] +mod tests { + use super::{AckDurability, Semantics}; + use crate::config::{BatcherConfig, CoreConfig}; + use crate::{AgentFS, AgentFSOptions, FileSystem, DEFAULT_FILE_MODE, S_IFREG}; + use std::sync::Arc; + use std::time::Duration; + + fn long_batch_window_config() -> CoreConfig { + CoreConfig { + batcher: BatcherConfig { + enabled: true, + window: Duration::from_secs(60), + inode_bytes: 1024 * 1024, + global_bytes: 16 * 1024 * 1024, + txn_max_inodes: 1024, + txn_max_bytes: 16 * 1024 * 1024, + }, + overlay_reads: true, + ..CoreConfig::default() + } + } + + #[tokio::test] + async fn committed_write_and_inode_barrier_share_the_durability_contract( + ) -> crate::error::Result<()> { + let dir = tempfile::tempdir()?; + let db_path = dir.path().join("semantics-durability.db"); + let agent = AgentFS::open( + AgentFSOptions::with_path(db_path.to_str().expect("test DB path is UTF-8")) + .with_core_config(long_batch_window_config()), + ) + .await?; + let fs: Arc = Arc::new(agent.fs); + let semantics = Semantics::new(fs.clone()); + + let (stats, file) = fs + .create_file(1, "committed.txt", S_IFREG | 0o644, 0, 0) + .await?; + let committed = semantics + .write(&file, 0, b"committed bytes", AckDurability::Committed) + .await?; + assert_eq!(committed.durability, AckDurability::Committed); + assert_eq!(committed.count, "committed bytes".len()); + let attr = semantics + .stat_coherent(stats.ino) + .await? + .expect("committed file should have attrs"); + assert_eq!(attr.size, "committed bytes".len() as i64); + + let (volatile_stats, volatile_file) = fs + .create_file(1, "volatile.txt", DEFAULT_FILE_MODE, 0, 0) + .await?; + let volatile = semantics + .write(&volatile_file, 0, b"barrier bytes", AckDurability::Volatile) + .await?; + assert_eq!(volatile.durability, AckDurability::Volatile); + assert_eq!(volatile.count, "barrier bytes".len()); + let attr = semantics + .stat_coherent(volatile_stats.ino) + .await? + .expect("volatile write should be visible to coherent attrs"); + assert_eq!(attr.size, "barrier bytes".len() as i64); + semantics.commit_barrier(Some(volatile_stats.ino)).await?; + + drop(file); + drop(volatile_file); + drop(semantics); + drop(fs); + + let reopened = AgentFS::open(AgentFSOptions::with_path( + db_path.to_str().expect("test DB path is UTF-8"), + )) + .await?; + let committed_stats = reopened + .fs + .lookup(1, "committed.txt") + .await? + .expect("committed file should reopen"); + let committed_file = + FileSystem::open(&reopened.fs, committed_stats.ino, libc::O_RDONLY).await?; + assert_eq!( + committed_file.pread(0, 64).await?, + b"committed bytes", + "Committed writes must survive reopening without a timer drain" + ); + let volatile_stats = reopened + .fs + .lookup(1, "volatile.txt") + .await? + .expect("barrier file should reopen"); + let volatile_file = + FileSystem::open(&reopened.fs, volatile_stats.ino, libc::O_RDONLY).await?; + assert_eq!( + volatile_file.pread(0, 64).await?, + b"barrier bytes", + "commit_barrier(Some(ino)) must make a volatile write durable" + ); + Ok(()) + } +} diff --git a/crates/agentfs-core/src/telemetry/mod.rs b/crates/agentfs-core/src/telemetry/mod.rs new file mode 100644 index 00000000..55d78d09 --- /dev/null +++ b/crates/agentfs-core/src/telemetry/mod.rs @@ -0,0 +1,789 @@ +//! Lightweight env-gated telemetry counters for AgentFS hot paths. +//! +//! Recording remains a single cached `AGENTFS_PROFILE` branch followed by +//! relaxed atomic updates. Counter sections are declared from compact tables so +//! core and adapter-specific vocabularies can live in their owning crates while +//! still being reported by one process-wide registry. + +use parking_lot::Mutex; +use serde::Serialize; +use std::collections::BTreeMap; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::time::{Duration, Instant}; + +#[cfg(not(test))] +static ENABLED: std::sync::OnceLock = std::sync::OnceLock::new(); + +static SUMMARY_EMITTED: AtomicBool = AtomicBool::new(false); +static CHECKPOINT_SEQ: AtomicU64 = AtomicU64::new(0); +static REGISTRY: std::sync::OnceLock>> = + std::sync::OnceLock::new(); + +pub const DEFAULT_PROFILE_ENABLED: bool = false; + +/// A named domain of telemetry counters. +pub trait TelemetrySection: Sync { + fn name(&self) -> &'static str; + fn snapshot(&self) -> BTreeMap; +} + +/// Process-wide registry for telemetry sections owned outside the SDK. +pub struct Registry; + +impl Registry { + pub fn register(section: &'static dyn TelemetrySection) { + let mut sections = registry_sections().lock(); + if !sections + .iter() + .any(|existing| existing.name() == section.name()) + { + sections.push(section); + } + } + + pub fn snapshot() -> ProfileSnapshot { + snapshot() + } +} + +fn registry_sections() -> &'static Mutex> { + REGISTRY.get_or_init(|| Mutex::new(Vec::new())) +} + +/// A point-in-time telemetry snapshot grouped by domain. +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] +pub struct ProfileSnapshot { + pub sections: BTreeMap>, +} + +impl ProfileSnapshot { + pub fn flat(&self) -> BTreeMap { + let mut counters = BTreeMap::new(); + for section in self.sections.values() { + counters.extend(section.iter().map(|(key, value)| (key.clone(), *value))); + } + counters + } + + pub fn section(&self, name: &str) -> Option<&BTreeMap> { + self.sections.get(name) + } + + pub fn counter(&self, name: &str) -> u64 { + self.flat().get(name).copied().unwrap_or(0) + } +} + +/// Monotonic counter: increment by one. +#[derive(Debug)] +pub struct Counter { + name: &'static str, + value: AtomicU64, +} + +impl Counter { + pub const fn new(name: &'static str) -> Self { + Self { + name, + value: AtomicU64::new(0), + } + } + + pub fn increment(&self) { + self.value.fetch_add(1, Ordering::Relaxed); + } + + #[doc(hidden)] + pub fn snapshot_into(&self, out: &mut BTreeMap) { + out.insert(self.name.to_string(), self.value.load(Ordering::Relaxed)); + } +} + +/// Monotonic summing counter. +#[derive(Debug)] +pub struct Sum { + name: &'static str, + value: AtomicU64, +} + +impl Sum { + pub const fn new(name: &'static str) -> Self { + Self { + name, + value: AtomicU64::new(0), + } + } + + pub fn add(&self, amount: u64) { + self.value.fetch_add(amount, Ordering::Relaxed); + } + + #[doc(hidden)] + pub fn snapshot_into(&self, out: &mut BTreeMap) { + out.insert(self.name.to_string(), self.value.load(Ordering::Relaxed)); + } +} + +/// Duration counter, emitted as `_count` plus `_nanos`. +#[derive(Debug)] +pub struct Timer { + name: &'static str, + count: AtomicU64, + nanos: AtomicU64, +} + +impl Timer { + pub const fn new(name: &'static str) -> Self { + Self { + name, + count: AtomicU64::new(0), + nanos: AtomicU64::new(0), + } + } + + pub fn record(&self, duration: Duration) { + self.count.fetch_add(1, Ordering::Relaxed); + self.nanos + .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed); + } + + #[doc(hidden)] + pub fn snapshot_into(&self, out: &mut BTreeMap) { + out.insert( + format!("{}_count", self.name), + self.count.load(Ordering::Relaxed), + ); + out.insert( + format!("{}_nanos", self.name), + self.nanos.load(Ordering::Relaxed), + ); + } +} + +/// Max-observed counter using relaxed compare-and-swap. +#[derive(Debug)] +pub struct Max { + name: &'static str, + value: AtomicU64, +} + +impl Max { + pub const fn new(name: &'static str) -> Self { + Self { + name, + value: AtomicU64::new(0), + } + } + + pub fn update(&self, candidate: u64) { + let mut current = self.value.load(Ordering::Relaxed); + while candidate > current { + match self.value.compare_exchange_weak( + current, + candidate, + Ordering::Relaxed, + Ordering::Relaxed, + ) { + Ok(_) => break, + Err(actual) => current = actual, + } + } + } + + #[doc(hidden)] + pub fn snapshot_into(&self, out: &mut BTreeMap) { + out.insert(self.name.to_string(), self.value.load(Ordering::Relaxed)); + } +} + +/// Last-observed configuration or state value. +#[derive(Debug)] +pub struct Gauge { + name: &'static str, + value: AtomicU64, +} + +impl Gauge { + pub const fn new(name: &'static str) -> Self { + Self { + name, + value: AtomicU64::new(0), + } + } + + pub fn set(&self, value: u64) { + self.value.store(value, Ordering::Relaxed); + } + + #[doc(hidden)] + pub fn snapshot_into(&self, out: &mut BTreeMap) { + out.insert(self.name.to_string(), self.value.load(Ordering::Relaxed)); + } +} + +/// RAII timer guard. Disabled profiling constructs a zero-sized no-op guard. +#[derive(Debug, Default)] +pub struct TimerGuard { + slot: Option<&'static Timer>, + started: Option, +} + +impl TimerGuard { + pub fn disarmed() -> Self { + Self::default() + } +} + +impl Drop for TimerGuard { + fn drop(&mut self) { + if let (Some(slot), Some(started)) = (self.slot, self.started.take()) { + slot.record(started.elapsed()); + } + } +} + +/// Start an env-gated RAII timer for a telemetry slot. +pub fn timer(slot: &'static Timer) -> TimerGuard { + if is_enabled() { + TimerGuard { + slot: Some(slot), + started: Some(Instant::now()), + } + } else { + TimerGuard::disarmed() + } +} + +#[macro_export] +macro_rules! define_counters { + ( + $(#[$meta:meta])* + $vis:vis static $static_name:ident: $type_name:ident = $section:literal { + $($counter:ident: $shape:ident),* $(,)? + } + ) => { + $(#[$meta])* + #[derive(Debug)] + $vis struct $type_name { + $(pub $counter: $crate::telemetry::$shape,)* + } + + impl $type_name { + pub const fn new() -> Self { + Self { + $($counter: $crate::telemetry::$shape::new(stringify!($counter)),)* + } + } + } + + impl Default for $type_name { + fn default() -> Self { + Self::new() + } + } + + $vis static $static_name: $type_name = $type_name::new(); + + impl $crate::telemetry::TelemetrySection for $type_name { + fn name(&self) -> &'static str { + $section + } + + fn snapshot(&self) -> std::collections::BTreeMap { + let mut out = std::collections::BTreeMap::new(); + $(self.$counter.snapshot_into(&mut out);)* + out + } + } + }; +} + +pub use crate::define_counters; + +crate::telemetry::define_counters! { + /// SDK/core-owned counters. Adapter vocabulary is registered by adapter crates. + pub static CORE_COUNTERS: CoreCounters = "core" { + connection_wait: Timer, + connection_create: Counter, + connection_reuse: Counter, + connection_drop_discards: Counter, + connection_health_evictions: Counter, + lookup_count: Counter, + lookup_delta_count: Counter, + lookup_base_count: Counter, + lookup_whiteout_count: Counter, + getattr_count: Counter, + readdir_count: Counter, + readdir_plus_count: Counter, + path_resolution_count: Counter, + path_component_count: Sum, + path_cache_hits: Counter, + path_cache_misses: Counter, + negative_lookup_count: Counter, + negative_cache_hits: Counter, + negative_cache_misses: Counter, + negative_cache_invalidations: Counter, + attr_cache_hits: Counter, + attr_cache_misses: Counter, + dentry_cache_hits: Counter, + dentry_cache_misses: Counter, + chunk_read_queries: Counter, + chunk_read_chunks: Sum, + chunk_write_chunks: Sum, + agentfs_batcher_enqueues: Counter, + agentfs_batcher_drains_timer: Counter, + agentfs_batcher_drains_bytes: Counter, + agentfs_batcher_drains_explicit: Counter, + agentfs_batcher_pending_max_bytes: Max, + agentfs_batcher_coalesced_ranges: Sum, + agentfs_batcher_commit_latency_ns_total: Sum, + agentfs_batcher_commit_txns: Counter, + agentfs_batcher_txn_inodes_total: Sum, + agentfs_batcher_txn_inodes_max: Max, + wal_checkpoint: Timer, + } +} + +crate::telemetry::define_counters! { + /// Base-file fast-path counters emitted by core overlay/host integration. + pub static BASE_COUNTERS: BaseCounters = "base" { + base_fast_open_eligible: Counter, + base_fast_open_keep_cache: Counter, + base_fast_open_passthrough_attempted: Counter, + base_fast_open_passthrough_succeeded: Counter, + base_fast_open_passthrough_fallback: Counter, + base_fast_open_rejected: Counter, + base_fast_inode_invalidations: Counter, + base_fast_stale_rejections: Counter, + } +} + +/// Returns true when profiling is enabled with `AGENTFS_PROFILE=1`. +/// Always-on under `#[cfg(test)]` so unit tests can assert on counters without +/// racing the global `OnceLock` init. +pub fn is_enabled() -> bool { + #[cfg(test)] + { + true + } + #[cfg(not(test))] + { + *ENABLED.get_or_init(|| { + crate::config::EnvReader::new().bool("AGENTFS_PROFILE", DEFAULT_PROFILE_ENABLED) + }) + } +} + +pub fn record_connection_wait(duration: Duration) { + if is_enabled() { + CORE_COUNTERS.connection_wait.record(duration); + } +} + +pub fn record_connection_create() { + if is_enabled() { + CORE_COUNTERS.connection_create.increment(); + } +} + +pub fn record_connection_reuse() { + if is_enabled() { + CORE_COUNTERS.connection_reuse.increment(); + } +} + +pub fn record_connection_drop_discard() { + if is_enabled() { + CORE_COUNTERS.connection_drop_discards.increment(); + } +} + +pub fn record_connection_health_eviction() { + if is_enabled() { + CORE_COUNTERS.connection_health_evictions.increment(); + } +} + +pub fn record_lookup() { + if is_enabled() { + CORE_COUNTERS.lookup_count.increment(); + } +} + +pub fn record_lookup_delta() { + if is_enabled() { + CORE_COUNTERS.lookup_delta_count.increment(); + } +} + +pub fn record_lookup_base() { + if is_enabled() { + CORE_COUNTERS.lookup_base_count.increment(); + } +} + +pub fn record_lookup_whiteout() { + if is_enabled() { + CORE_COUNTERS.lookup_whiteout_count.increment(); + } +} + +pub fn record_getattr() { + if is_enabled() { + CORE_COUNTERS.getattr_count.increment(); + } +} + +pub fn record_readdir() { + if is_enabled() { + CORE_COUNTERS.readdir_count.increment(); + } +} + +pub fn record_readdir_plus() { + if is_enabled() { + CORE_COUNTERS.readdir_plus_count.increment(); + } +} + +pub fn record_path_resolution(components: u64) { + if is_enabled() { + CORE_COUNTERS.path_resolution_count.increment(); + CORE_COUNTERS.path_component_count.add(components); + } +} + +pub fn record_path_cache_hit() { + if is_enabled() { + CORE_COUNTERS.path_cache_hits.increment(); + } +} + +pub fn record_path_cache_miss() { + if is_enabled() { + CORE_COUNTERS.path_cache_misses.increment(); + } +} + +pub fn record_negative_lookup() { + if is_enabled() { + CORE_COUNTERS.negative_lookup_count.increment(); + } +} + +pub fn record_negative_cache_hit() { + if is_enabled() { + CORE_COUNTERS.negative_cache_hits.increment(); + } +} + +pub fn record_negative_cache_miss() { + if is_enabled() { + CORE_COUNTERS.negative_cache_misses.increment(); + } +} + +pub fn record_negative_cache_invalidation() { + if is_enabled() { + CORE_COUNTERS.negative_cache_invalidations.increment(); + } +} + +pub fn record_attr_cache_hit() { + if is_enabled() { + CORE_COUNTERS.attr_cache_hits.increment(); + } +} + +pub fn record_attr_cache_miss() { + if is_enabled() { + CORE_COUNTERS.attr_cache_misses.increment(); + } +} + +pub fn record_dentry_cache_hit() { + if is_enabled() { + CORE_COUNTERS.dentry_cache_hits.increment(); + } +} + +pub fn record_dentry_cache_miss() { + if is_enabled() { + CORE_COUNTERS.dentry_cache_misses.increment(); + } +} + +pub fn record_chunk_read_query() { + if is_enabled() { + CORE_COUNTERS.chunk_read_queries.increment(); + } +} + +pub fn record_chunk_read_chunks(chunks: u64) { + if is_enabled() { + CORE_COUNTERS.chunk_read_chunks.add(chunks); + } +} + +pub fn record_chunk_write_chunks(chunks: u64) { + if is_enabled() { + CORE_COUNTERS.chunk_write_chunks.add(chunks); + } +} + +pub fn record_agentfs_batcher_enqueue() { + if is_enabled() { + CORE_COUNTERS.agentfs_batcher_enqueues.increment(); + } +} + +pub fn record_agentfs_batcher_drain_timer() { + if is_enabled() { + CORE_COUNTERS.agentfs_batcher_drains_timer.increment(); + } +} + +pub fn record_agentfs_batcher_drain_bytes() { + if is_enabled() { + CORE_COUNTERS.agentfs_batcher_drains_bytes.increment(); + } +} + +pub fn record_agentfs_batcher_drain_explicit() { + if is_enabled() { + CORE_COUNTERS.agentfs_batcher_drains_explicit.increment(); + } +} + +pub fn record_agentfs_batcher_pending_bytes(pending_bytes: u64) { + if is_enabled() { + CORE_COUNTERS + .agentfs_batcher_pending_max_bytes + .update(pending_bytes); + } +} + +pub fn record_agentfs_batcher_coalesced_ranges(ranges: u64) { + if is_enabled() && ranges > 0 { + CORE_COUNTERS.agentfs_batcher_coalesced_ranges.add(ranges); + } +} + +pub fn record_agentfs_batcher_commit_latency(duration: Duration) { + if is_enabled() { + CORE_COUNTERS + .agentfs_batcher_commit_latency_ns_total + .add(duration.as_nanos() as u64); + } +} + +/// Record one batcher SQLite commit transaction that covered `inodes` inodes. +pub fn record_agentfs_batcher_commit_txn(inodes: u64) { + if is_enabled() { + CORE_COUNTERS.agentfs_batcher_commit_txns.increment(); + CORE_COUNTERS.agentfs_batcher_txn_inodes_total.add(inodes); + CORE_COUNTERS.agentfs_batcher_txn_inodes_max.update(inodes); + } +} + +pub fn record_wal_checkpoint(duration: Duration) { + if is_enabled() { + CORE_COUNTERS.wal_checkpoint.record(duration); + } +} + +pub fn record_base_fast_open_eligible() { + if is_enabled() { + BASE_COUNTERS.base_fast_open_eligible.increment(); + } +} + +pub fn record_base_fast_open_keep_cache() { + if is_enabled() { + BASE_COUNTERS.base_fast_open_keep_cache.increment(); + } +} + +pub fn record_base_fast_open_passthrough_attempted() { + if is_enabled() { + BASE_COUNTERS + .base_fast_open_passthrough_attempted + .increment(); + } +} + +pub fn record_base_fast_open_passthrough_succeeded() { + if is_enabled() { + BASE_COUNTERS + .base_fast_open_passthrough_succeeded + .increment(); + } +} + +pub fn record_base_fast_open_passthrough_fallback() { + if is_enabled() { + BASE_COUNTERS + .base_fast_open_passthrough_fallback + .increment(); + } +} + +pub fn record_base_fast_open_rejected() { + if is_enabled() { + BASE_COUNTERS.base_fast_open_rejected.increment(); + } +} + +pub fn record_base_fast_inode_invalidation() { + if is_enabled() { + BASE_COUNTERS.base_fast_inode_invalidations.increment(); + } +} + +pub fn record_base_fast_stale_rejection() { + if is_enabled() { + BASE_COUNTERS.base_fast_stale_rejections.increment(); + } +} + +pub fn snapshot() -> ProfileSnapshot { + let mut sections = BTreeMap::new(); + sections.insert(CORE_COUNTERS.name().to_string(), CORE_COUNTERS.snapshot()); + sections.insert(BASE_COUNTERS.name().to_string(), BASE_COUNTERS.snapshot()); + for section in registry_sections().lock().iter() { + sections.insert(section.name().to_string(), section.snapshot()); + } + ProfileSnapshot { sections } +} + +pub const fn passthrough_supported() -> bool { + false +} + +pub const fn passthrough_fallback_read_path() -> &'static str { + "hostfs" +} + +pub fn summary_payload(event: &str, source: &str, snapshot: &ProfileSnapshot) -> String { + serde_json::json!({ + "event": event, + "source": source, + "sections": &snapshot.sections, + "counters": snapshot.flat(), + "passthrough_supported": passthrough_supported(), + "fallback_read_path": passthrough_fallback_read_path(), + }) + .to_string() +} + +/// Return the process summary payload at most once. +pub fn take_summary_payload(event: &str, source: &str) -> Option { + if !is_enabled() { + return None; + } + + if SUMMARY_EMITTED + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) + .is_ok() + { + Some(summary_payload(event, source, &snapshot())) + } else { + None + } +} + +/// Return a cumulative profile checkpoint payload tagged with a sequence number. +pub fn checkpoint_payload(event: &str) -> Option { + if !is_enabled() { + return None; + } + + let seq = CHECKPOINT_SEQ.fetch_add(1, Ordering::Relaxed) + 1; + Some(summary_payload( + event, + &format!("phase-checkpoint-{seq}"), + &snapshot(), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::Value; + + #[test] + fn macro_counters_accumulate_expected_values() { + crate::telemetry::define_counters! { + static TEST_COUNTERS: TestCounters = "test" { + hits: Counter, + bytes: Sum, + latency: Timer, + peak: Max, + current: Gauge, + } + } + + TEST_COUNTERS.hits.increment(); + TEST_COUNTERS.bytes.add(5); + TEST_COUNTERS.bytes.add(7); + TEST_COUNTERS.latency.record(Duration::from_nanos(11)); + TEST_COUNTERS.latency.record(Duration::from_nanos(13)); + TEST_COUNTERS.peak.update(4); + TEST_COUNTERS.peak.update(3); + TEST_COUNTERS.current.set(9); + + let section = TEST_COUNTERS.snapshot(); + assert_eq!(section["hits"], 1); + assert_eq!(section["bytes"], 12); + assert_eq!(section["latency_count"], 2); + assert_eq!(section["latency_nanos"], 24); + assert_eq!(section["peak"], 4); + assert_eq!(section["current"], 9); + } + + #[test] + fn snapshot_has_core_and_base_sections() { + record_connection_wait(Duration::from_nanos(7)); + record_connection_create(); + record_base_fast_open_eligible(); + + let snapshot = snapshot(); + assert!(snapshot.section("core").is_some()); + assert!(snapshot.section("base").is_some()); + assert!(snapshot.counter("connection_wait_count") >= 1); + assert!(snapshot.counter("connection_wait_nanos") >= 7); + assert!(snapshot.counter("connection_create") >= 1); + assert!(snapshot.counter("base_fast_open_eligible") >= 1); + } + + #[test] + fn summary_json_is_structured_with_sections_and_flat_counters() { + let mut sections = BTreeMap::new(); + sections.insert( + "core".to_string(), + BTreeMap::from([("connection_create".to_string(), 1)]), + ); + let snapshot = ProfileSnapshot { sections }; + let value: Value = serde_json::from_str(&summary_payload( + "unit_profile_summary", + "unit-test", + &snapshot, + )) + .expect("summary json should parse"); + + assert_eq!(value["event"], "unit_profile_summary"); + assert_eq!(value["source"], "unit-test"); + assert_eq!(value["sections"]["core"]["connection_create"], 1); + assert_eq!(value["counters"]["connection_create"], 1); + } + + #[test] + fn sdk_only_usage_does_not_emit_report() { + record_connection_create(); + let snapshot = snapshot(); + assert!(snapshot.counter("connection_create") >= 1); + // No report guard is constructed here. The validation harness runs this + // test with --nocapture and asserts no CLI profile summary line is + // printed by SDK-only usage. + } +} diff --git a/sdk/rust/src/toolcalls.rs b/crates/agentfs-core/src/toolcalls.rs similarity index 59% rename from sdk/rust/src/toolcalls.rs rename to crates/agentfs-core/src/toolcalls.rs index 955cb53c..a8c0d7ca 100644 --- a/sdk/rust/src/toolcalls.rs +++ b/crates/agentfs-core/src/toolcalls.rs @@ -1,5 +1,6 @@ -use crate::connection_pool::ConnectionPool; use crate::error::{Error, Result}; +use crate::pool::{ConnectionPool, DatabaseType}; +use crate::schema; use serde::{Deserialize, Serialize}; use std::{ fmt, @@ -26,12 +27,17 @@ impl fmt::Display for ToolCallStatus { } } -impl From<&str> for ToolCallStatus { - fn from(s: &str) -> Self { +impl TryFrom<&str> for ToolCallStatus { + type Error = Error; + + fn try_from(s: &str) -> Result { match s { - "success" => ToolCallStatus::Success, - "error" => ToolCallStatus::Error, - _ => ToolCallStatus::Pending, + "pending" => Ok(ToolCallStatus::Pending), + "success" => Ok(ToolCallStatus::Success), + "error" => Ok(ToolCallStatus::Error), + other => Err(Error::Internal(format!( + "invalid tool call status: {other}" + ))), } } } @@ -75,7 +81,12 @@ impl ToolCalls { /// Create a new tool calls tracker pub async fn new(db_path: &str) -> Result { let db = Builder::new_local(db_path).build().await?; - let pool = ConnectionPool::new(db); + let options = if db_path == ":memory:" { + crate::fs::agentfs::memory_connection_pool_options() + } else { + crate::fs::agentfs::file_backed_connection_pool_options() + }; + let pool = ConnectionPool::with_options(DatabaseType::Local(db), options); let tc = Self { pool }; tc.initialize().await?; Ok(tc) @@ -91,37 +102,7 @@ impl ToolCalls { /// Initialize the database schema async fn initialize(&self) -> Result<()> { let conn = self.pool.get_connection().await?; - conn.execute( - "CREATE TABLE IF NOT EXISTS tool_calls ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - parameters TEXT, - result TEXT, - error TEXT, - status TEXT NOT NULL DEFAULT 'pending', - started_at INTEGER NOT NULL, - completed_at INTEGER, - duration_ms INTEGER - )", - (), - ) - .await?; - - conn.execute( - "CREATE INDEX IF NOT EXISTS idx_tool_calls_name - ON tool_calls(name)", - (), - ) - .await?; - - conn.execute( - "CREATE INDEX IF NOT EXISTS idx_tool_calls_started_at - ON tool_calls(started_at)", - (), - ) - .await?; - - Ok(()) + schema::require_current(&conn).await } /// Start a new tool call and mark it as pending @@ -129,7 +110,7 @@ impl ToolCalls { pub async fn start(&self, name: &str, parameters: Option) -> Result { let conn = self.pool.get_connection().await?; let serialized_params = parameters.map(|p| serde_json::to_string(&p)).transpose()?; - let started_at = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64; + let started_at = unix_timestamp_ms()?; let mut stmt = conn .prepare_cached( @@ -153,7 +134,7 @@ impl ToolCalls { pub async fn success(&self, id: i64, result: Option) -> Result<()> { let conn = self.pool.get_connection().await?; let serialized_result = result.map(|r| serde_json::to_string(&r)).transpose()?; - let completed_at = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64; + let completed_at = unix_timestamp_ms()?; // Get the started_at time to calculate duration let mut stmt = conn @@ -170,7 +151,7 @@ impl ToolCalls { return Err(Error::ToolCallNotFound); }; - let duration_ms = (completed_at - started_at) * 1000; + let duration_ms = duration_ms_between(started_at, completed_at); let mut stmt = conn .prepare_cached( @@ -205,7 +186,7 @@ impl ToolCalls { let conn = self.pool.get_connection().await?; let serialized_params = parameters.map(|p| serde_json::to_string(&p)).transpose()?; let serialized_result = result.map(|r| serde_json::to_string(&r)).transpose()?; - let duration_ms = (completed_at - started_at) * 1000; + let duration_ms = duration_ms_between(started_at, completed_at); let status = if error.is_some() { "error" } else { "success" }; let mut stmt = conn @@ -238,7 +219,7 @@ impl ToolCalls { /// Mark a tool call as failed pub async fn error(&self, id: i64, error: &str) -> Result<()> { let conn = self.pool.get_connection().await?; - let completed_at = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64; + let completed_at = unix_timestamp_ms()?; // Get the started_at time to calculate duration let mut stmt = conn @@ -255,7 +236,7 @@ impl ToolCalls { return Err(Error::ToolCallNotFound); }; - let duration_ms = (completed_at - started_at) * 1000; + let duration_ms = duration_ms_between(started_at, completed_at); let mut stmt = conn .prepare_cached( @@ -361,81 +342,16 @@ impl ToolCalls { } fn row_to_tool_call(row: &turso::Row) -> Result { - let id = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - - let name = row - .get_value(1) - .ok() - .and_then(|v| { - if let Value::Text(s) = v { - Some(s.clone()) - } else { - None - } - }) - .unwrap_or_default(); - - let parameters = row.get_value(2).ok().and_then(|v| { - if let Value::Text(s) = v { - if !s.is_empty() { - serde_json::from_str(s.as_str()).ok() - } else { - None - } - } else { - None - } - }); - - let result = row.get_value(3).ok().and_then(|v| { - if let Value::Text(s) = v { - if !s.is_empty() { - serde_json::from_str(s.as_str()).ok() - } else { - None - } - } else { - None - } - }); - - let error = row.get_value(4).ok().and_then(|v| { - if let Value::Text(s) = v { - if !s.is_empty() { - Some(s.clone()) - } else { - None - } - } else { - None - } - }); - - let status = row - .get_value(5) - .ok() - .and_then(|v| { - if let Value::Text(s) = v { - Some(ToolCallStatus::from(s.as_str())) - } else { - None - } - }) - .unwrap_or(ToolCallStatus::Pending); - - let started_at = row - .get_value(6) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - - let completed_at = row.get_value(7).ok().and_then(|v| v.as_integer().copied()); - - let duration_ms = row.get_value(8).ok().and_then(|v| v.as_integer().copied()); + let id = required_i64(row, 0, "id")?; + let name = required_text(row, 1, "name")?; + let parameters = optional_json(row, 2, "parameters")?; + let result = optional_json(row, 3, "result")?; + let error = optional_text(row, 4, "error")?; + let status = ToolCallStatus::try_from(required_text(row, 5, "status")?.as_str())?; + let started_at = normalize_timestamp_for_output(required_i64(row, 6, "started_at")?); + let completed_at = + optional_i64(row, 7, "completed_at")?.map(normalize_timestamp_for_output); + let duration_ms = optional_i64(row, 8, "duration_ms")?; Ok(ToolCall { id, @@ -451,45 +367,11 @@ impl ToolCalls { } fn row_to_stats(row: &turso::Row) -> Result { - let name = row - .get_value(0) - .ok() - .and_then(|v| { - if let Value::Text(s) = v { - Some(s.clone()) - } else { - None - } - }) - .unwrap_or_default(); - - let total_calls = row - .get_value(1) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - - let successful = row - .get_value(2) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - - let failed = row - .get_value(3) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - - let avg_duration_ms = row - .get_value(4) - .ok() - .and_then(|v| match v { - Value::Real(f) => Some(f), - Value::Integer(i) => Some(i as f64), - _ => None, - }) - .unwrap_or(0.0); + let name = required_text(row, 0, "name")?; + let total_calls = required_i64(row, 1, "total_calls")?; + let successful = required_i64(row, 2, "successful")?; + let failed = required_i64(row, 3, "failed")?; + let avg_duration_ms = required_f64(row, 4, "avg_duration_ms")?; Ok(ToolCallStats { name, @@ -500,3 +382,173 @@ impl ToolCalls { }) } } + +fn unix_timestamp_ms() -> Result { + SystemTime::now() + .duration_since(UNIX_EPOCH)? + .as_millis() + .try_into() + .map_err(|_| Error::Internal("current timestamp exceeds i64 milliseconds".to_string())) +} + +fn duration_ms_between(started_at: i64, completed_at: i64) -> i64 { + timestamp_ms(completed_at).saturating_sub(timestamp_ms(started_at)) +} + +fn timestamp_ms(timestamp: i64) -> i64 { + if timestamp.abs() >= 1_000_000_000_000 { + timestamp + } else { + timestamp.saturating_mul(1000) + } +} + +fn normalize_timestamp_for_output(timestamp: i64) -> i64 { + if timestamp.abs() >= 1_000_000_000_000 { + timestamp / 1000 + } else { + timestamp + } +} + +fn required_i64(row: &turso::Row, idx: usize, column: &str) -> Result { + row.get_value(idx) + .ok() + .and_then(|v| v.as_integer().copied()) + .ok_or_else(|| Error::Internal(format!("invalid tool_calls.{column}: expected integer"))) +} + +fn optional_i64(row: &turso::Row, idx: usize, column: &str) -> Result> { + match row.get_value(idx).ok() { + Some(Value::Null) | None => Ok(None), + Some(value) => value.as_integer().copied().map(Some).ok_or_else(|| { + Error::Internal(format!("invalid tool_calls.{column}: expected integer")) + }), + } +} + +fn required_text(row: &turso::Row, idx: usize, column: &str) -> Result { + optional_text(row, idx, column)? + .ok_or_else(|| Error::Internal(format!("invalid tool_calls.{column}: expected text"))) +} + +fn optional_text(row: &turso::Row, idx: usize, column: &str) -> Result> { + match row.get_value(idx).ok() { + Some(Value::Null) | None => Ok(None), + Some(Value::Text(s)) if s.is_empty() => Ok(None), + Some(Value::Text(s)) => Ok(Some(s.clone())), + Some(_) => Err(Error::Internal(format!( + "invalid tool_calls.{column}: expected text" + ))), + } +} + +fn optional_json(row: &turso::Row, idx: usize, column: &str) -> Result> { + optional_text(row, idx, column)? + .map(|s| serde_json::from_str(s.as_str()).map_err(Error::from)) + .transpose() +} + +fn required_f64(row: &turso::Row, idx: usize, column: &str) -> Result { + match row.get_value(idx).ok() { + Some(Value::Real(value)) => Ok(value), + Some(Value::Integer(value)) => Ok(value as f64), + _ => Err(Error::Internal(format!( + "invalid tool_calls.{column}: expected number" + ))), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + + #[tokio::test] + async fn start_success_records_millisecond_duration() -> Result<()> { + let dir = tempdir()?; + let db_path = dir.path().join("tools.db"); + let tools = ToolCalls::new(db_path.to_str().unwrap()).await?; + + let id = tools.start("fast", None).await?; + tokio::time::sleep(std::time::Duration::from_millis(25)).await; + tools.success(id, None).await?; + + let call = tools.get(id).await?.expect("tool call should exist"); + let duration_ms = call.duration_ms.expect("completed call has duration"); + assert!( + (20..2000).contains(&duration_ms), + "duration_ms should be millisecond-scaled, got {duration_ms}" + ); + assert!( + call.completed_at.unwrap() >= call.started_at, + "decoded timestamps should remain unix seconds for consumers" + ); + Ok(()) + } + + #[tokio::test] + async fn timestamps_stored_in_ms_decode_to_unix_seconds() -> Result<()> { + let dir = tempdir()?; + let db_path = dir.path().join("tools.db"); + let tools = ToolCalls::new(db_path.to_str().unwrap()).await?; + + let id = tools + .record( + "clock", + 1_600_000_000_000, + 1_600_000_000_500, + None, + None, + None, + ) + .await?; + + let call = tools.get(id).await?.expect("tool call should exist"); + assert_eq!(call.started_at, 1_600_000_000); + assert_eq!(call.completed_at, Some(1_600_000_000)); + assert_eq!(call.duration_ms, Some(500)); + + let conn = tools.pool.get_connection().await?; + let mut stmt = conn + .prepare_cached("SELECT started_at FROM tool_calls WHERE id = ?") + .await?; + let row = stmt.query_row((id,)).await?; + let stored = row + .get_value(0) + .ok() + .and_then(|v| v.as_integer().copied()) + .expect("started_at should be an integer"); + assert_eq!( + stored, 1_600_000_000_000, + "storage stays millisecond-granular; only decode normalizes" + ); + Ok(()) + } + + #[tokio::test] + async fn invalid_toolcall_json_propagates() -> Result<()> { + let dir = tempdir()?; + let db_path = dir.path().join("tools.db"); + let tools = ToolCalls::new(db_path.to_str().unwrap()).await?; + let id = tools.start("bad-json", None).await?; + + let conn = tools.pool.get_connection().await?; + conn.execute( + "UPDATE tool_calls SET parameters = ? WHERE id = ?", + ("{not-json", id), + ) + .await?; + drop(conn); + + let error = tools + .get(id) + .await + .expect_err("invalid JSON should propagate"); + assert!( + matches!(error, Error::Json(_)), + "expected JSON decode error, got {error:?}" + ); + Ok(()) + } +} diff --git a/sdk/rust/tests/concurrency_integrity.rs b/crates/agentfs-core/tests/concurrency_integrity.rs similarity index 98% rename from sdk/rust/tests/concurrency_integrity.rs rename to crates/agentfs-core/tests/concurrency_integrity.rs index bf61c504..2f06849b 100644 --- a/sdk/rust/tests/concurrency_integrity.rs +++ b/crates/agentfs-core/tests/concurrency_integrity.rs @@ -1,5 +1,5 @@ -use agentfs_sdk::error::Result; -use agentfs_sdk::{AgentFS, AgentFSOptions, DEFAULT_FILE_MODE}; +use agentfs_core::error::Result; +use agentfs_core::{AgentFS, AgentFSOptions, DEFAULT_FILE_MODE}; use serde_json::{json, Value}; use std::sync::{ atomic::{AtomicUsize, Ordering}, @@ -134,7 +134,7 @@ async fn concurrent_sdk_operations_preserve_database_integrity() -> Result<()> { handle.await.expect("worker task panicked")?; } - agent.fs.fsync("/").await?; + agent.fs.fsync().await?; assert_integrity_check_ok(&agent).await?; assert_final_state(&agent).await?; diff --git a/crates/agentfs-core/tests/host_layout.rs b/crates/agentfs-core/tests/host_layout.rs new file mode 100644 index 00000000..3e1d91e3 --- /dev/null +++ b/crates/agentfs-core/tests/host_layout.rs @@ -0,0 +1,29 @@ +use std::fs; +use std::path::Path; + +#[test] +fn host_file_impl_lives_in_common_module() { + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let host_dir = manifest_dir.join("src/fs/host"); + let common = fs::read_to_string(host_dir.join("common.rs")) + .expect("fs/host/common.rs should hold the shared HostFSFile implementation"); + let linux = fs::read_to_string(host_dir.join("linux.rs")).expect("read linux hostfs module"); + let darwin = fs::read_to_string(host_dir.join("darwin.rs")).expect("read darwin hostfs module"); + + assert!( + common.contains("pub(super) struct HostFSFile"), + "HostFSFile should be declared once in common.rs" + ); + assert!( + common.contains("impl File for HostFSFile"), + "the shared File impl should live in common.rs" + ); + assert!( + !linux.contains("impl File for HostFSFile"), + "linux.rs should consume common::HostFSFile, not duplicate the File impl" + ); + assert!( + !darwin.contains("impl File for HostFSFile"), + "darwin.rs should consume common::HostFSFile, not duplicate the File impl" + ); +} diff --git a/sdk/rust/tests/snapshot_restore.rs b/crates/agentfs-core/tests/snapshot_restore.rs similarity index 81% rename from sdk/rust/tests/snapshot_restore.rs rename to crates/agentfs-core/tests/snapshot_restore.rs index f8ad09b3..e79aba66 100644 --- a/sdk/rust/tests/snapshot_restore.rs +++ b/crates/agentfs-core/tests/snapshot_restore.rs @@ -1,8 +1,11 @@ -use agentfs_sdk::error::Result; -use agentfs_sdk::{AgentFS, AgentFSOptions, ToolCallStatus, DEFAULT_FILE_MODE}; +use agentfs_core::error::Result; +use agentfs_core::fs::{AgentFS as AgentFsCore, FileSystem, FsError}; +use agentfs_core::{AgentFS, AgentFSOptions, ToolCallStatus, DEFAULT_FILE_MODE}; use serde_json::{json, Value}; use std::path::{Path, PathBuf}; +const ROOT_INO: i64 = 1; + #[derive(Debug, Clone)] struct SnapshotCase { seed: usize, @@ -44,7 +47,7 @@ async fn snapshot_restore_preserves_one_file_agent_state_after_checkpoint() -> R assert_generated_state(&agent, &cases, &tool_ids).await?; assert_integrity_check_ok(&agent).await?; - agent.fs.fsync("/").await?; + agent.fs.fsync().await?; assert_journal_mode_is_wal(&agent).await?; assert_wal_sidecar_checkpointed(&source_db); @@ -91,7 +94,7 @@ async fn create_snapshot_case( .await?; crossing_file.pwrite(patch_offset as u64, &patch).await?; - agent.fs.link(&crossing_path, &hardlink_path).await?; + link_path(&agent.fs, &crossing_path, &hardlink_path).await?; let inline_data = patterned_bytes(512 + seed, 0x30 + seed as u8); let (_, inline_file) = agent @@ -110,13 +113,17 @@ async fn create_snapshot_case( agent .fs - .symlink( - "nested/crossing.bin", - &symlink_path, - seed as u32, - seed as u32, - ) - .await?; + .stat(&nested_dir) + .await? + .expect("nested dir should exist"); + create_symlink_path( + &agent.fs, + "nested/crossing.bin", + &symlink_path, + seed as u32, + seed as u32, + ) + .await?; agent .kv @@ -238,7 +245,7 @@ async fn assert_generated_state( expected_sparse.extend_from_slice(&case.sparse_tail); assert_eq!(sparse_contents, expected_sparse); - let symlink_stats = agent.fs.lstat(&case.symlink_path).await?.unwrap(); + let symlink_stats = lstat_path(&agent.fs, &case.symlink_path).await?.unwrap(); assert!(symlink_stats.is_symlink()); assert_eq!( agent.fs.readlink(&case.symlink_path).await?, @@ -314,6 +321,70 @@ async fn assert_generated_state( Ok(()) } +async fn parent_and_name(fs: &AgentFsCore, path: &str) -> Result<(i64, String)> { + let normalized = normalize_test_path(path); + let components = normalized + .split('/') + .filter(|part| !part.is_empty()) + .collect::>(); + if components.is_empty() { + return Err(FsError::RootOperation.into()); + } + let name = components.last().unwrap().to_string(); + let parent_path = if components.len() == 1 { + "/".to_string() + } else { + format!("/{}", components[..components.len() - 1].join("/")) + }; + let parent_ino = if parent_path == "/" { + ROOT_INO + } else { + fs.stat(&parent_path).await?.ok_or(FsError::NotFound)?.ino + }; + Ok((parent_ino, name)) +} + +fn normalize_test_path(path: &str) -> String { + let trimmed = path.trim_end_matches('/'); + if trimmed.is_empty() { + "/".to_string() + } else if trimmed.starts_with('/') { + trimmed.to_string() + } else { + format!("/{trimmed}") + } +} + +async fn link_path(fs: &AgentFsCore, oldpath: &str, newpath: &str) -> Result<()> { + let source = fs.stat(oldpath).await?.ok_or(FsError::NotFound)?; + let (newparent_ino, newname) = parent_and_name(fs, newpath).await?; + FileSystem::link(fs, source.ino, newparent_ino, &newname) + .await + .map(|_| ()) +} + +async fn create_symlink_path( + fs: &AgentFsCore, + target: &str, + linkpath: &str, + uid: u32, + gid: u32, +) -> Result<()> { + let (parent_ino, name) = parent_and_name(fs, linkpath).await?; + FileSystem::symlink(fs, parent_ino, &name, target, uid, gid) + .await + .map(|_| ()) +} + +async fn lstat_path(fs: &AgentFsCore, path: &str) -> Result> { + let normalized = normalize_test_path(path); + if normalized == "/" { + return FileSystem::getattr(fs, ROOT_INO).await; + } + let (parent_ino, name) = parent_and_name(fs, &normalized).await?; + FileSystem::lookup(fs, parent_ino, &name).await +} + async fn assert_integrity_check_ok(agent: &AgentFS) -> Result<()> { let conn = agent.get_connection().await?; let mut rows = conn.query("PRAGMA integrity_check", ()).await?; @@ -338,6 +409,7 @@ async fn assert_inline_inode_has_no_chunks( ino: i64, expected: &[u8], ) -> Result<()> { + agent.fs.drain_all().await?; let conn = agent.get_connection().await?; let mut rows = conn .query( diff --git a/crates/agentfs-fuse/Cargo.toml b/crates/agentfs-fuse/Cargo.toml new file mode 100644 index 00000000..76c9bd92 --- /dev/null +++ b/crates/agentfs-fuse/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "agentfs-fuse" +version.workspace = true +edition.workspace = true +description = "Linux FUSE adapter for AgentFS" +license.workspace = true + +[lints] +workspace = true + +[dependencies] +agentfs-core = { workspace = true } +anyhow = { workspace = true } +libc = { workspace = true } +memchr = { workspace = true } +nix = { workspace = true } +page_size = { workspace = true } +parking_lot = { workspace = true } +smallvec = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +turso = { workspace = true } +zerocopy = { workspace = true } + +[dev-dependencies] +async-trait = { workspace = true } +tempfile = { workspace = true } diff --git a/crates/agentfs-fuse/src/adapter/cache.rs b/crates/agentfs-fuse/src/adapter/cache.rs new file mode 100644 index 00000000..505aba56 --- /dev/null +++ b/crates/agentfs-fuse/src/adapter/cache.rs @@ -0,0 +1,361 @@ +//! Adapter-side kernel cache coherence state (`AdapterCaches`). +//! +//! Lock order: `reply_lock` -> one cache map lock at a time (`dir_entries`, +//! `attr`, `entry`, `negative_entry`, `external_read_*`, `keepcache_drift`). +//! Mutations go through [`AdapterCaches::mutate`], which holds `reply_lock` +//! while bumping the epoch and touching map locks sequentially; cacheable +//! replies hold only [`CacheReplyGuard`]. Map locks never nest with each +//! other, and no guard is held across backend `block_on`/`.await` calls +//! (`clippy::await_holding_lock` is deny-by-workspace). + +use crate::transport::FileAttr; +use agentfs_core::Stats; +use parking_lot::{Mutex, MutexGuard}; +use std::collections::{HashMap, HashSet}; +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, +}; + +/// Directory entry materialized for readdir/readdirplus cache replies. +pub(super) struct CachedDirEntry { + pub(super) name: String, + pub(super) attr: FileAttr, +} + +/// Freshness fingerprint for FOPEN_KEEP_CACHE eligibility. +#[derive(Debug, Clone, Eq, PartialEq)] +pub(super) struct KeepCacheFingerprint { + mode: u32, + size: i64, + mtime: i64, + mtime_nsec: u32, + ctime: i64, + ctime_nsec: u32, + rdev: u64, +} + +impl KeepCacheFingerprint { + pub(super) fn from_stats(stats: &Stats) -> Self { + Self { + mode: stats.mode, + size: stats.size, + mtime: stats.mtime, + mtime_nsec: stats.mtime_nsec, + ctime: stats.ctime, + ctime_nsec: stats.ctime_nsec, + rdev: stats.rdev, + } + } +} + +/// Guards `FOPEN_KEEP_CACHE` grants against serving stale kernel pages. +/// +/// Non-sticky (default): a mutation drops the stored fingerprint and the next +/// read-only open revalidates against fresh stats. This is sound because +/// every mutation path is kernel-originated: the kernel's own pages stay +/// coherent for its own writes, and adapter-notified invalidations purge them. +#[derive(Debug, Default)] +struct KeepCacheDriftGuard { + sticky: bool, + dropped: HashSet, + fingerprints: HashMap, +} + +impl KeepCacheDriftGuard { + fn new(sticky: bool) -> Self { + Self { + sticky, + ..Self::default() + } + } + + fn allows(&self, ino: u64, fingerprint: &KeepCacheFingerprint) -> bool { + !self.dropped.contains(&ino) + && self + .fingerprints + .get(&ino) + .map(|existing| existing == fingerprint) + .unwrap_or(true) + } + + fn mark_eligible(&mut self, ino: u64, fingerprint: KeepCacheFingerprint) { + if !self.dropped.contains(&ino) { + self.fingerprints.insert(ino, fingerprint); + } + } + + fn drop_eligibility(&mut self, ino: u64) -> bool { + let had_fingerprint = self.fingerprints.remove(&ino).is_some(); + if self.sticky { + self.dropped.insert(ino) || had_fingerprint + } else { + had_fingerprint + } + } +} + +/// Whether a cache mutation should also notify the kernel. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(super) enum NotifyPolicy { + NotifyKernel, + SuppressKernel, +} + +impl NotifyPolicy { + fn should_notify_kernel(self) -> bool { + matches!(self, Self::NotifyKernel) + } +} + +pub(super) struct InodeInvalidation { + pub(super) dropped_keepcache: bool, + pub(super) notify_kernel: bool, +} + +pub(super) struct EntryInvalidation { + pub(super) removed_negative: bool, + pub(super) notify_kernel: bool, +} + +/// Reply-lock guard retained while a cacheable FUSE reply is emitted. +pub(super) struct CacheReplyGuard<'a> { + _guard: MutexGuard<'a, ()>, +} + +/// Adapter-owned kernel-cache coherence state. +/// +/// All epoch checks and mutation invalidations pass through this API so cache +/// reply locks are never held across backend `block_on` calls. Callers perform +/// async work first, then acquire [`CacheReplyGuard`] or call [`Self::mutate`]. +pub(super) struct AdapterCaches { + dir_entries: Arc>>>>, + attr: Arc>>, + entry: Arc>>, + negative_entry: Arc>>, + external_read_fingerprints: Arc>>, + external_read_seen: Arc>>, + keepcache_drift: Arc>, + reply_lock: Arc>, + epoch: AtomicU64, +} + +impl AdapterCaches { + pub(super) fn new(keepcache_sticky_drop: bool) -> Self { + Self { + dir_entries: Arc::new(Mutex::new(HashMap::new())), + attr: Arc::new(Mutex::new(HashMap::new())), + entry: Arc::new(Mutex::new(HashMap::new())), + negative_entry: Arc::new(Mutex::new(HashMap::new())), + external_read_fingerprints: Arc::new(Mutex::new(HashMap::new())), + external_read_seen: Arc::new(Mutex::new(HashSet::new())), + keepcache_drift: Arc::new(Mutex::new(KeepCacheDriftGuard::new(keepcache_sticky_drop))), + reply_lock: Arc::new(Mutex::new(())), + epoch: AtomicU64::new(0), + } + } + + pub(super) fn epoch(&self) -> u64 { + self.epoch.load(Ordering::Acquire) + } + + pub(super) fn epoch_changed(&self, epoch: u64) -> bool { + self.epoch.load(Ordering::Acquire) != epoch + } + + pub(super) fn try_reply_guard(&self, snapshot_epoch: u64) -> Option> { + let guard = self.reply_lock.try_lock()?; + if self.epoch_changed(snapshot_epoch) { + return None; + } + Some(CacheReplyGuard { _guard: guard }) + } + + pub(super) fn mutate(&self, policy: NotifyPolicy, f: impl FnOnce(&Self)) -> bool { + let _reply = self.reply_lock.lock(); + self.epoch.fetch_add(1, Ordering::AcqRel); + f(self); + policy.should_notify_kernel() + } + + pub(super) fn invalidate_inode(&self, ino: u64, policy: NotifyPolicy) -> InodeInvalidation { + let mut dropped_keepcache = false; + let notify_kernel = self.mutate(policy, |caches| { + dropped_keepcache = caches.drop_keepcache_eligibility(ino); + caches.prune_external_read_state(ino); + caches.attrs().lock().remove(&ino); + caches + .entries() + .lock() + .retain(|_, stats| stats.ino as u64 != ino); + caches.dir_entries().lock().retain(|dir_ino, entries| { + *dir_ino != ino && !entries.iter().any(|entry| entry.attr.ino == ino) + }); + }); + InodeInvalidation { + dropped_keepcache, + notify_kernel, + } + } + + pub(super) fn invalidate_entry( + &self, + parent: u64, + name: &str, + policy: NotifyPolicy, + ) -> EntryInvalidation { + let mut removed_negative = false; + let notify_kernel = self.mutate(policy, |caches| { + let key = (parent, name.to_string()); + caches.entries().lock().remove(&key); + removed_negative = caches.negative_entries().lock().remove(&key).is_some(); + }); + EntryInvalidation { + removed_negative, + notify_kernel, + } + } + + pub(super) fn dir_entries(&self) -> &Mutex>>> { + &self.dir_entries + } + + pub(super) fn attrs(&self) -> &Mutex> { + &self.attr + } + + pub(super) fn entries(&self) -> &Mutex> { + &self.entry + } + + pub(super) fn negative_entries(&self) -> &Mutex> { + &self.negative_entry + } + + pub(super) fn keepcache_allows(&self, ino: u64, fingerprint: &KeepCacheFingerprint) -> bool { + self.keepcache_drift.lock().allows(ino, fingerprint) + } + + pub(super) fn mark_keepcache_eligible(&self, ino: u64, fingerprint: KeepCacheFingerprint) { + self.keepcache_drift.lock().mark_eligible(ino, fingerprint); + } + + pub(super) fn drop_keepcache_eligibility(&self, ino: u64) -> bool { + self.keepcache_drift.lock().drop_eligibility(ino) + } + + pub(super) fn set_external_read_fingerprint( + &self, + ino: u64, + fingerprint: KeepCacheFingerprint, + ) { + self.external_read_fingerprints + .lock() + .insert(ino, fingerprint); + } + + pub(super) fn mark_external_read_seen(&self, ino: u64) { + self.external_read_seen.lock().insert(ino); + } + + pub(super) fn external_read_was_seen(&self, ino: u64) -> bool { + self.external_read_seen.lock().contains(&ino) + } + + pub(super) fn prune_external_read_state(&self, ino: u64) { + self.external_read_fingerprints.lock().remove(&ino); + self.external_read_seen.lock().remove(&ino); + } + + pub(super) fn external_read_drifted( + &self, + ino: u64, + fingerprint: KeepCacheFingerprint, + ) -> bool { + let mut fingerprints = self.external_read_fingerprints.lock(); + match fingerprints.get(&ino) { + Some(existing) if existing != &fingerprint => { + fingerprints.insert(ino, fingerprint); + true + } + Some(_) => false, + None => { + fingerprints.insert(ino, fingerprint); + false + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn stats_with_size(size: i64) -> Stats { + Stats { + ino: 42, + mode: libc::S_IFREG | 0o644, + nlink: 1, + uid: 1000, + gid: 1000, + size, + atime: 1, + mtime: 2, + ctime: 3, + atime_nsec: 4, + mtime_nsec: 5, + ctime_nsec: 6, + rdev: 0, + } + } + + #[test] + fn notify_policy_controls_mutation_notify_flag() { + let caches = AdapterCaches::new(false); + + assert!(caches.mutate(NotifyPolicy::NotifyKernel, |_| {})); + assert!(!caches.mutate(NotifyPolicy::SuppressKernel, |_| {})); + } + + #[test] + fn external_read_drift_revalidates_after_one_error() { + let caches = AdapterCaches::new(false); + let ino = 7; + caches.mark_external_read_seen(ino); + + let before = KeepCacheFingerprint::from_stats(&stats_with_size(5)); + let after = KeepCacheFingerprint::from_stats(&stats_with_size(9)); + + assert!( + !caches.external_read_drifted(ino, before.clone()), + "first fingerprint records the baseline" + ); + assert!( + caches.external_read_drifted(ino, after.clone()), + "changed fingerprint reports one drift error" + ); + assert!( + !caches.external_read_drifted(ino, after), + "the changed fingerprint becomes the new validated baseline" + ); + } + + #[test] + fn prune_external_read_state_removes_seen_and_fingerprint() { + let caches = AdapterCaches::new(false); + let ino = 11; + let fingerprint = KeepCacheFingerprint::from_stats(&stats_with_size(5)); + + caches.mark_external_read_seen(ino); + caches.set_external_read_fingerprint(ino, fingerprint.clone()); + assert!(caches.external_read_was_seen(ino)); + + caches.prune_external_read_state(ino); + + assert!(!caches.external_read_was_seen(ino)); + assert!( + !caches.external_read_drifted(ino, fingerprint), + "pruned fingerprint should behave like a fresh baseline" + ); + } +} diff --git a/crates/agentfs-fuse/src/adapter/config.rs b/crates/agentfs-fuse/src/adapter/config.rs new file mode 100644 index 00000000..c80792cd --- /dev/null +++ b/crates/agentfs-fuse/src/adapter/config.rs @@ -0,0 +1,894 @@ +//! Typed FUSE runtime configuration. +//! +//! This is the single environment-reading edge for Linux FUSE adapter and +//! transport knobs. The adapter and session receive values from [`FuseConfig`] +//! instead of reading process environment directly. + +use std::env::VarError; +use std::time::Duration; + +use agentfs_core::EnvReader; + +/// The max size of write requests plus header slack used by the FUSE session +/// request buffer. Dispatch auto-sizing must account for one buffer per +/// worker plus queued request. +pub(crate) const FUSE_REQUEST_BUFFER_SIZE: usize = (16 * 1024 * 1024) + 4096; + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(crate) enum FuseWorkersDefault { + Auto, +} + +impl FuseWorkersDefault { + pub(crate) const fn as_str(self) -> &'static str { + match self { + Self::Auto => "auto", + } + } + + fn matches(self, value: &str) -> bool { + value.trim().eq_ignore_ascii_case(self.as_str()) + } + + fn resolve(self) -> usize { + match self { + Self::Auto => workers_from_resource_percent( + env_percent("AGENTFS_FUSE_CPU_PERCENT", DEFAULT_AUTO_PERCENT), + env_percent("AGENTFS_FUSE_MEMORY_PERCENT", DEFAULT_AUTO_PERCENT), + ), + } + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(crate) enum FuseQueueDefault { + Derived, +} + +impl FuseQueueDefault { + pub(crate) const fn as_str(self) -> &'static str { + match self { + Self::Derived => "derived", + } + } + + fn matches(self, value: &str) -> bool { + value.trim().eq_ignore_ascii_case(self.as_str()) + } + + fn resolve(self, workers: usize) -> usize { + match self { + Self::Derived => derived_queue_capacity(workers), + } + } +} + +/// Default kernel TTLs for positive dentries and attributes. 10s lets a whole +/// git-style workload (clone ≈3s + status/diff/fsck) reuse the dentries and +/// attrs established by mutation replies instead of re-LOOKUP/GETATTR storms +/// (warm steady-state reads measured 12.7x native at the old 1s default). +/// Within one mount the kernel is coherent for its own operations regardless +/// of TTL; the TTL only bounds staleness ACROSS concurrent mounts of the same +/// session DB (`agentfs run --session` from another terminal), which now see +/// attribute/namespace changes within 10s. Override with +/// `AGENTFS_FUSE_ENTRY_TTL_MS` / `AGENTFS_FUSE_ATTR_TTL_MS`. +pub(crate) const DEFAULT_FUSE_POSITIVE_TTL_MS: u64 = 10_000; +/// Default kernel TTL for negative dentries. Kept at 1s: a file created by a +/// second mount stays invisible to this mount for the negative TTL, and +/// lookup-miss caching is the most surprising staleness to debug. Override +/// with `AGENTFS_FUSE_NEG_TTL_MS`. +pub(crate) const DEFAULT_FUSE_NEG_TTL_MS: u64 = 1000; +pub(crate) const DEFAULT_AUTO_PERCENT: u8 = 50; +pub(crate) const DEFAULT_QUEUE_MEMORY_PERCENT: u8 = 25; +pub(crate) const DEFAULT_INO_FILES_CAP: usize = 65_536; +pub(crate) const DEFAULT_URING_DEPTH: usize = 4; +pub(crate) const DEFAULT_FUSE_WORKERS: FuseWorkersDefault = FuseWorkersDefault::Auto; +pub(crate) const DEFAULT_FUSE_QUEUE: FuseQueueDefault = FuseQueueDefault::Derived; +pub(crate) const DEFAULT_FUSE_WRITEBACK: bool = true; +pub(crate) const DEFAULT_FUSE_KEEPCACHE: bool = true; +pub(crate) const DEFAULT_FUSE_SYNC_INVAL: bool = false; +pub(crate) const DEFAULT_FUSE_SELF_INVAL: bool = false; +pub(crate) const DEFAULT_DRAIN_ON_RELEASE: bool = false; +pub(crate) const DEFAULT_DRAIN_ON_FORGET: bool = false; +pub(crate) const DEFAULT_FUSE_FLUSH_INVAL: bool = false; +pub(crate) const DEFAULT_FUSE_NOFLUSH: bool = true; +pub(crate) const DEFAULT_FUSE_NOOPEN: bool = true; +pub(crate) const DEFAULT_FUSE_CACHE_DIR: bool = true; +pub(crate) const DEFAULT_FUSE_STICKY_KEEPCACHE_DROP: bool = false; +const MAX_URING_DEPTH: usize = 64; +const MAX_URING_SPIN_US: u64 = 1000; + +const READDIRPLUS_MODE_OFF: u64 = 0; +const READDIRPLUS_MODE_AUTO: u64 = 1; +const READDIRPLUS_MODE_ALWAYS: u64 = 2; + +/// Runtime request dispatch mode for the FUSE session. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum DispatchMode { + Serial, + Parallel { + workers: usize, + queue_capacity: usize, + }, +} + +impl DispatchMode { + fn from_env() -> Self { + let workers = match std::env::var("AGENTFS_FUSE_WORKERS") { + Ok(value) if value.eq_ignore_ascii_case("serial") => return Self::Serial, + Ok(value) if DEFAULT_FUSE_WORKERS.matches(&value) => DEFAULT_FUSE_WORKERS.resolve(), + Ok(value) => parse_workers(&value).unwrap_or_else(|| { + tracing::warn!( + value, + "invalid AGENTFS_FUSE_WORKERS; using serial FUSE dispatch" + ); + 0 + }), + Err(VarError::NotPresent) => DEFAULT_FUSE_WORKERS.resolve(), + Err(VarError::NotUnicode(value)) => { + tracing::warn!( + ?value, + "invalid AGENTFS_FUSE_WORKERS; using serial FUSE dispatch" + ); + 0 + } + }; + + if workers == 0 { + return Self::Serial; + } + + let default_queue_capacity = default_queue_capacity(workers); + let queue_capacity = match std::env::var("AGENTFS_FUSE_QUEUE") { + Ok(value) if DEFAULT_FUSE_QUEUE.matches(&value) => default_queue_capacity, + Ok(value) => parse_queue_capacity(&value, workers).unwrap_or_else(|| { + tracing::warn!( + value, + default_queue_capacity, + "invalid AGENTFS_FUSE_QUEUE; using default queue capacity" + ); + default_queue_capacity + }), + Err(VarError::NotPresent) => default_queue_capacity, + Err(VarError::NotUnicode(value)) => { + tracing::warn!( + ?value, + default_queue_capacity, + "invalid AGENTFS_FUSE_QUEUE; using default queue capacity" + ); + default_queue_capacity + } + }; + + Self::Parallel { + workers, + queue_capacity, + } + } + + pub(crate) const fn is_serial(self) -> bool { + matches!(self, Self::Serial) + } +} + +/// Kernel readdirplus policy. +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] +pub enum ReaddirPlusMode { + Off, + Auto, + #[default] + Always, +} + +impl ReaddirPlusMode { + pub(crate) fn profile_value(self) -> u64 { + match self { + ReaddirPlusMode::Off => READDIRPLUS_MODE_OFF, + ReaddirPlusMode::Auto => READDIRPLUS_MODE_AUTO, + ReaddirPlusMode::Always => READDIRPLUS_MODE_ALWAYS, + } + } +} + +/// Effective kernel-cache policy after applying the dispatch safety interlock. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct FuseKernelCacheConfig { + pub(crate) entry_ttl: Duration, + pub(crate) attr_ttl: Duration, + pub(crate) neg_ttl: Duration, + pub(crate) entry_ttl_ms: u64, + pub(crate) attr_ttl_ms: u64, + pub(crate) neg_ttl_ms: u64, + pub(crate) writeback_cache_enabled: bool, + pub(crate) keepcache_enabled: bool, + pub(crate) readdirplus_mode: ReaddirPlusMode, +} + +impl FuseKernelCacheConfig { + pub(crate) fn record_profile(&self) { + crate::telemetry::set_fuse_ttl_ms(self.entry_ttl_ms, self.attr_ttl_ms, self.neg_ttl_ms); + crate::telemetry::set_fuse_keepcache_enabled(self.keepcache_enabled); + crate::telemetry::set_fuse_readdirplus_mode(self.readdirplus_mode.profile_value()); + } +} + +/// FUSE-over-io_uring runtime settings. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct UringConfig { + pub(crate) enabled: bool, + pub(crate) depth: usize, + pub(crate) spin_us: u64, +} + +impl Default for UringConfig { + fn default() -> Self { + Self { + enabled: true, + depth: DEFAULT_URING_DEPTH, + spin_us: 0, + } + } +} + +impl UringConfig { + fn from_env(reader: EnvReader) -> Self { + let default = Self::default(); + Self { + enabled: reader.bool("AGENTFS_FUSE_URING", default.enabled), + depth: env_usize_in_range( + "AGENTFS_FUSE_URING_DEPTH", + default.depth, + 1, + MAX_URING_DEPTH, + ), + spin_us: env_u64_in_range( + "AGENTFS_FUSE_URING_SPIN_US", + default.spin_us, + 0, + MAX_URING_SPIN_US, + ), + } + } +} + +/// Parsed FUSE adapter/session configuration. Fields that need safety +/// interlocks store the requested env values; use [`FuseConfig::kernel_cache`] +/// for effective cache values. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct FuseConfig { + pub(crate) dispatch_mode: DispatchMode, + pub(crate) entry_ttl_ms: u64, + pub(crate) attr_ttl_ms: u64, + pub(crate) neg_ttl_ms: u64, + pub(crate) writeback_cache_requested: bool, + pub(crate) keepcache_requested: bool, + pub(crate) readdirplus_requested: ReaddirPlusMode, + pub(crate) sync_inval: bool, + pub(crate) self_inval: bool, + pub(crate) drain_on_release: bool, + pub(crate) drain_on_forget: bool, + pub(crate) flush_inval_always: bool, + pub(crate) noflush: bool, + pub(crate) noopen: bool, + pub(crate) ino_files_cap: usize, + pub(crate) cache_dir_requested: bool, + pub(crate) keepcache_sticky_drop: bool, + pub(crate) uring: UringConfig, +} + +impl FuseConfig { + pub(crate) fn from_env() -> Self { + let reader = EnvReader::new(); + let dispatch_mode = DispatchMode::from_env(); + let drain_on_release = reader.bool("AGENTFS_DRAIN_ON_RELEASE", DEFAULT_DRAIN_ON_RELEASE); + let noflush_requested = reader.bool("AGENTFS_FUSE_NOFLUSH", DEFAULT_FUSE_NOFLUSH); + let noflush = noflush_requested && !drain_on_release; + if noflush_requested && !noflush { + tracing::warn!( + "AGENTFS_FUSE_NOFLUSH disabled: AGENTFS_DRAIN_ON_RELEASE needs the close-time FLUSH" + ); + } + let noopen_requested = reader.bool("AGENTFS_FUSE_NOOPEN", DEFAULT_FUSE_NOOPEN); + let noopen = noopen_requested && !drain_on_release; + if noopen_requested && !noopen { + tracing::warn!( + "AGENTFS_FUSE_NOOPEN disabled: AGENTFS_DRAIN_ON_RELEASE needs per-handle releases" + ); + } + + let config = Self { + dispatch_mode, + entry_ttl_ms: env_duration_ms( + "AGENTFS_FUSE_ENTRY_TTL_MS", + DEFAULT_FUSE_POSITIVE_TTL_MS, + ), + attr_ttl_ms: env_duration_ms("AGENTFS_FUSE_ATTR_TTL_MS", DEFAULT_FUSE_POSITIVE_TTL_MS), + neg_ttl_ms: env_duration_ms("AGENTFS_FUSE_NEG_TTL_MS", DEFAULT_FUSE_NEG_TTL_MS), + writeback_cache_requested: reader + .bool("AGENTFS_FUSE_WRITEBACK", DEFAULT_FUSE_WRITEBACK), + keepcache_requested: reader.bool("AGENTFS_FUSE_KEEPCACHE", DEFAULT_FUSE_KEEPCACHE), + readdirplus_requested: readdirplus_mode_from_env(), + sync_inval: sync_inval_from_env(reader, dispatch_mode), + self_inval: reader.bool("AGENTFS_FUSE_SELF_INVAL", DEFAULT_FUSE_SELF_INVAL), + drain_on_release, + drain_on_forget: reader.bool("AGENTFS_DRAIN_ON_FORGET", DEFAULT_DRAIN_ON_FORGET), + flush_inval_always: reader.bool("AGENTFS_FUSE_FLUSH_INVAL", DEFAULT_FUSE_FLUSH_INVAL), + noflush, + noopen, + ino_files_cap: env_usize_min("AGENTFS_FUSE_INO_FILES_CAP", DEFAULT_INO_FILES_CAP, 16), + cache_dir_requested: reader.bool("AGENTFS_FUSE_CACHE_DIR", DEFAULT_FUSE_CACHE_DIR), + keepcache_sticky_drop: reader.bool( + "AGENTFS_FUSE_STICKY_KEEPCACHE_DROP", + DEFAULT_FUSE_STICKY_KEEPCACHE_DROP, + ), + uring: UringConfig::from_env(reader), + }; + emit_kernel_cache_interlock_warnings(&config); + config + } + + pub(crate) fn kernel_cache(&self) -> FuseKernelCacheConfig { + cache_safety_interlock(self) + } + + pub(crate) fn cache_dir_enabled(&self) -> bool { + self.cache_dir_requested && self.kernel_cache().keepcache_enabled + } +} + +/// Pure cache-safety interlock: serial dispatch cannot safely use kernel +/// TTLs, writeback, keepcache, or readdirplus because invalidation progress +/// depends on a worker distinct from the request reader. +pub(crate) fn cache_safety_interlock(config: &FuseConfig) -> FuseKernelCacheConfig { + if config.dispatch_mode.is_serial() { + return FuseKernelCacheConfig { + entry_ttl: Duration::ZERO, + attr_ttl: Duration::ZERO, + neg_ttl: Duration::ZERO, + entry_ttl_ms: 0, + attr_ttl_ms: 0, + neg_ttl_ms: 0, + writeback_cache_enabled: false, + keepcache_enabled: false, + readdirplus_mode: ReaddirPlusMode::Off, + }; + } + + FuseKernelCacheConfig { + entry_ttl: Duration::from_millis(config.entry_ttl_ms), + attr_ttl: Duration::from_millis(config.attr_ttl_ms), + neg_ttl: Duration::from_millis(config.neg_ttl_ms), + entry_ttl_ms: config.entry_ttl_ms, + attr_ttl_ms: config.attr_ttl_ms, + neg_ttl_ms: config.neg_ttl_ms, + writeback_cache_enabled: config.writeback_cache_requested, + keepcache_enabled: config.keepcache_requested, + readdirplus_mode: config.readdirplus_requested, + } +} + +fn emit_kernel_cache_interlock_warnings(config: &FuseConfig) { + if !config.dispatch_mode.is_serial() { + return; + } + + if config.entry_ttl_ms != 0 || config.attr_ttl_ms != 0 || config.neg_ttl_ms != 0 { + tracing::warn!( + "Refusing nonzero FUSE TTLs: kernel entry/attr/negative TTLs require non-serial AGENTFS_FUSE_WORKERS" + ); + } + if config.writeback_cache_requested { + tracing::warn!( + "Refusing FUSE writeback cache: AGENTFS_FUSE_WRITEBACK requires non-serial AGENTFS_FUSE_WORKERS" + ); + } + if config.keepcache_requested { + tracing::warn!( + "Refusing FOPEN_KEEP_CACHE: AGENTFS_FUSE_KEEPCACHE requires non-serial AGENTFS_FUSE_WORKERS" + ); + } + if config.readdirplus_requested != ReaddirPlusMode::Off { + tracing::warn!( + "Refusing FUSE readdirplus: readdirplus requires non-serial AGENTFS_FUSE_WORKERS" + ); + } +} + +fn readdirplus_mode_from_env() -> ReaddirPlusMode { + let default = ReaddirPlusMode::default(); + match std::env::var("AGENTFS_FUSE_READDIRPLUS") { + Ok(value) + if value.eq_ignore_ascii_case("off") + || value.eq_ignore_ascii_case("false") + || value.eq_ignore_ascii_case("no") + || value == "0" => + { + ReaddirPlusMode::Off + } + Ok(value) if value.eq_ignore_ascii_case("auto") => ReaddirPlusMode::Auto, + Ok(value) + if value.eq_ignore_ascii_case("always") + || value.eq_ignore_ascii_case("on") + || value.eq_ignore_ascii_case("true") + || value.eq_ignore_ascii_case("yes") + || value == "1" => + { + ReaddirPlusMode::Always + } + Ok(value) => { + tracing::warn!( + "Ignoring invalid AGENTFS_FUSE_READDIRPLUS={:?}; using default {:?}", + value, + default + ); + default + } + Err(VarError::NotPresent) => default, + Err(VarError::NotUnicode(value)) => { + tracing::warn!( + ?value, + "Ignoring non-Unicode AGENTFS_FUSE_READDIRPLUS; using default" + ); + default + } + } +} + +fn sync_inval_from_env(reader: EnvReader, dispatch_mode: DispatchMode) -> bool { + let sync_requested = reader.bool("AGENTFS_FUSE_SYNC_INVAL", DEFAULT_FUSE_SYNC_INVAL); + if dispatch_mode.is_serial() && sync_requested { + tracing::info!( + "AGENTFS_FUSE_SYNC_INVAL requested with AGENTFS_FUSE_WORKERS=serial; using deferred invalidation to avoid notify/reply deadlock" + ); + false + } else { + sync_requested + } +} + +fn env_duration_ms(name: &str, default: u64) -> u64 { + match std::env::var(name) { + Ok(value) => match value.trim().parse::() { + Ok(ms) => ms, + Err(_) => { + tracing::warn!( + "Ignoring invalid {}={:?} for FUSE TTL; using {}ms", + name, + value, + default + ); + default + } + }, + Err(VarError::NotPresent) => default, + Err(VarError::NotUnicode(value)) => { + tracing::warn!( + "Ignoring non-Unicode {}={:?} for FUSE TTL; using {}ms", + name, + value, + default + ); + default + } + } +} + +fn env_usize_min(name: &str, default: usize, min: usize) -> usize { + match std::env::var(name) { + Ok(value) => match value.trim().parse::().ok().filter(|v| *v >= min) { + Some(parsed) => parsed, + None => { + tracing::warn!( + name, + value, + default, + min, + "invalid FUSE usize config; using default" + ); + default + } + }, + Err(VarError::NotPresent) => default, + Err(VarError::NotUnicode(value)) => { + tracing::warn!( + name, + ?value, + default, + min, + "non-Unicode FUSE usize config; using default" + ); + default + } + } +} + +fn env_usize_in_range(name: &str, default: usize, min: usize, max: usize) -> usize { + match std::env::var(name) { + Ok(value) => match value + .trim() + .parse::() + .ok() + .filter(|v| (min..=max).contains(v)) + { + Some(parsed) => parsed, + None => { + tracing::warn!( + name, + value, + default, + min, + max, + "invalid FUSE usize config; using default" + ); + default + } + }, + Err(VarError::NotPresent) => default, + Err(VarError::NotUnicode(value)) => { + tracing::warn!( + name, + ?value, + default, + min, + max, + "non-Unicode FUSE usize config; using default" + ); + default + } + } +} + +fn env_u64_in_range(name: &str, default: u64, min: u64, max: u64) -> u64 { + match std::env::var(name) { + Ok(value) => match value + .trim() + .parse::() + .ok() + .filter(|v| (min..=max).contains(v)) + { + Some(parsed) => parsed, + None => { + tracing::warn!( + name, + value, + default, + min, + max, + "invalid FUSE u64 config; using default" + ); + default + } + }, + Err(VarError::NotPresent) => default, + Err(VarError::NotUnicode(value)) => { + tracing::warn!( + name, + ?value, + default, + min, + max, + "non-Unicode FUSE u64 config; using default" + ); + default + } + } +} + +fn parse_workers(value: &str) -> Option { + let value = value.trim(); + if let Some(percent) = parse_percent_suffix(value) { + return Some(workers_from_resource_percent( + percent, + env_percent("AGENTFS_FUSE_MEMORY_PERCENT", percent), + )); + } + value.parse::().ok().filter(|workers| *workers > 0) +} + +fn parse_queue_capacity(value: &str, workers: usize) -> Option { + let value = value.trim(); + if let Some(percent) = parse_percent_suffix(value) { + return Some(queue_capacity_for_memory_percent(workers, percent)); + } + value.parse::().ok().filter(|queue| *queue > 0) +} + +fn parse_percent_suffix(value: &str) -> Option { + let percent = value.strip_suffix('%')?.trim().parse::().ok()?; + (1..=100).contains(&percent).then_some(percent) +} + +fn parse_percent(value: &str) -> Option { + parse_percent_suffix(value.trim()).or_else(|| { + value + .trim() + .parse::() + .ok() + .filter(|v| (1..=100).contains(v)) + }) +} + +fn env_percent(name: &str, default: u8) -> u8 { + match std::env::var(name) { + Ok(value) => parse_percent(&value).unwrap_or_else(|| { + tracing::warn!( + name, + value, + default, + "invalid percent environment variable; using default" + ); + default + }), + Err(VarError::NotPresent) => default, + Err(VarError::NotUnicode(value)) => { + tracing::warn!( + name, + ?value, + default, + "non-Unicode percent environment variable; using default" + ); + default + } + } +} + +fn workers_from_resource_percent(cpu_percent: u8, memory_percent: u8) -> usize { + let cpu_workers = std::thread::available_parallelism() + .map(|parallelism| percent_of_count(parallelism.get(), cpu_percent)) + .unwrap_or(1); + let memory_workers = available_memory_bytes() + .map(|bytes| { + let budget = percent_of_bytes(bytes, memory_percent); + (budget / FUSE_REQUEST_BUFFER_SIZE as u64).max(1) as usize + }) + .unwrap_or(cpu_workers); + cpu_workers.min(memory_workers).max(1) +} + +fn default_queue_capacity(workers: usize) -> usize { + DEFAULT_FUSE_QUEUE.resolve(workers) +} + +fn derived_queue_capacity(workers: usize) -> usize { + let memory_percent = env_percent( + "AGENTFS_FUSE_QUEUE_MEMORY_PERCENT", + DEFAULT_QUEUE_MEMORY_PERCENT, + ); + workers + .saturating_mul(4) + .max(1) + .min(queue_capacity_for_memory_percent(workers, memory_percent)) +} + +fn queue_capacity_for_memory_percent(workers: usize, percent: u8) -> usize { + let Some(bytes) = available_memory_bytes() else { + return workers.saturating_mul(4).max(1); + }; + let budget = percent_of_bytes(bytes, percent); + let worker_bytes = workers.saturating_mul(FUSE_REQUEST_BUFFER_SIZE) as u64; + let queue_budget = budget.saturating_sub(worker_bytes); + (queue_budget / FUSE_REQUEST_BUFFER_SIZE as u64).max(1) as usize +} + +fn percent_of_count(count: usize, percent: u8) -> usize { + ((count as u64 * percent as u64) / 100).max(1) as usize +} + +fn percent_of_bytes(bytes: u64, percent: u8) -> u64 { + bytes.saturating_mul(percent as u64) / 100 +} + +fn available_memory_bytes() -> Option { + #[cfg(target_os = "linux")] + { + let meminfo = std::fs::read_to_string("/proc/meminfo").ok()?; + for line in meminfo.lines() { + let Some(rest) = line.strip_prefix("MemAvailable:") else { + continue; + }; + let kib = rest.split_whitespace().next()?.parse::().ok()?; + return kib.checked_mul(1024); + } + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Mutex; + + static ENV_LOCK: Mutex<()> = Mutex::new(()); + + const FUSE_ENV_KEYS: &[&str] = &[ + "AGENTFS_FUSE_WORKERS", + "AGENTFS_FUSE_CPU_PERCENT", + "AGENTFS_FUSE_MEMORY_PERCENT", + "AGENTFS_FUSE_QUEUE", + "AGENTFS_FUSE_QUEUE_MEMORY_PERCENT", + "AGENTFS_FUSE_ENTRY_TTL_MS", + "AGENTFS_FUSE_ATTR_TTL_MS", + "AGENTFS_FUSE_NEG_TTL_MS", + "AGENTFS_FUSE_WRITEBACK", + "AGENTFS_FUSE_KEEPCACHE", + "AGENTFS_FUSE_READDIRPLUS", + "AGENTFS_FUSE_SYNC_INVAL", + "AGENTFS_FUSE_SELF_INVAL", + "AGENTFS_DRAIN_ON_RELEASE", + "AGENTFS_DRAIN_ON_FORGET", + "AGENTFS_FUSE_FLUSH_INVAL", + "AGENTFS_FUSE_NOFLUSH", + "AGENTFS_FUSE_NOOPEN", + "AGENTFS_FUSE_INO_FILES_CAP", + "AGENTFS_FUSE_CACHE_DIR", + "AGENTFS_FUSE_STICKY_KEEPCACHE_DROP", + "AGENTFS_FUSE_URING", + "AGENTFS_FUSE_URING_DEPTH", + "AGENTFS_FUSE_URING_SPIN_US", + ]; + + struct EnvSnapshot { + values: Vec<(&'static str, Option)>, + } + + impl EnvSnapshot { + fn capture(keys: &[&'static str]) -> Self { + let values = keys + .iter() + .map(|key| (*key, std::env::var(key).ok())) + .collect(); + for key in keys { + std::env::remove_var(key); + } + Self { values } + } + } + + impl Drop for EnvSnapshot { + fn drop(&mut self) { + for (key, value) in &self.values { + match value { + Some(value) => std::env::set_var(key, value), + None => std::env::remove_var(key), + } + } + } + } + + fn default_test_config(dispatch_mode: DispatchMode) -> FuseConfig { + FuseConfig { + dispatch_mode, + entry_ttl_ms: DEFAULT_FUSE_POSITIVE_TTL_MS, + attr_ttl_ms: DEFAULT_FUSE_POSITIVE_TTL_MS, + neg_ttl_ms: DEFAULT_FUSE_NEG_TTL_MS, + writeback_cache_requested: true, + keepcache_requested: true, + readdirplus_requested: ReaddirPlusMode::Always, + sync_inval: false, + self_inval: false, + drain_on_release: false, + drain_on_forget: false, + flush_inval_always: false, + noflush: true, + noopen: true, + ino_files_cap: DEFAULT_INO_FILES_CAP, + cache_dir_requested: true, + keepcache_sticky_drop: false, + uring: UringConfig::default(), + } + } + + #[test] + fn documented_default_tokens_feed_runtime_parsers() { + let _lock = ENV_LOCK.lock().unwrap(); + let _snapshot = EnvSnapshot::capture(FUSE_ENV_KEYS); + + let unset_config = FuseConfig::from_env(); + let unset_workers = match unset_config.dispatch_mode { + DispatchMode::Parallel { workers, .. } => workers, + DispatchMode::Serial => 0, + }; + + std::env::set_var("AGENTFS_FUSE_WORKERS", DEFAULT_FUSE_WORKERS.as_str()); + let explicit_config = FuseConfig::from_env(); + let explicit_workers = match explicit_config.dispatch_mode { + DispatchMode::Parallel { workers, .. } => workers, + DispatchMode::Serial => 0, + }; + assert_eq!( + explicit_workers, unset_workers, + "documented worker default must use the same parser path as an unset env var" + ); + + std::env::set_var("AGENTFS_FUSE_WORKERS", "2"); + std::env::remove_var("AGENTFS_FUSE_QUEUE"); + let unset_queue_config = FuseConfig::from_env(); + + std::env::set_var("AGENTFS_FUSE_QUEUE", DEFAULT_FUSE_QUEUE.as_str()); + let explicit_queue_config = FuseConfig::from_env(); + + assert_eq!( + explicit_queue_config.dispatch_mode, unset_queue_config.dispatch_mode, + "documented queue default must use the same parser path as an unset env var" + ); + } + + #[test] + fn invalid_readdirplus_warns_and_defaults_on() { + let _lock = ENV_LOCK.lock().unwrap(); + let _snapshot = EnvSnapshot::capture(FUSE_ENV_KEYS); + std::env::set_var("AGENTFS_FUSE_READDIRPLUS", "bogus"); + + let config = FuseConfig::from_env(); + let cache = config.kernel_cache(); + + println!( + "invalid AGENTFS_FUSE_READDIRPLUS kept default {:?}", + cache.readdirplus_mode + ); + assert_eq!(cache.readdirplus_mode, ReaddirPlusMode::Always); + } + + #[test] + fn workers_config_feeds_dispatch_and_cache_policy() { + let _lock = ENV_LOCK.lock().unwrap(); + let _snapshot = EnvSnapshot::capture(FUSE_ENV_KEYS); + std::env::set_var("AGENTFS_FUSE_WORKERS", "25%"); + + let config = FuseConfig::from_env(); + let cache = config.kernel_cache(); + + let DispatchMode::Parallel { workers, .. } = config.dispatch_mode else { + panic!("25% workers should resolve to parallel dispatch"); + }; + println!( + "parsed DispatchMode::Parallel workers={workers}; cache writeback={}", + cache.writeback_cache_enabled + ); + assert!(workers > 0); + assert!(cache.writeback_cache_enabled); + assert!(cache.keepcache_enabled); + assert_ne!(cache.readdirplus_mode, ReaddirPlusMode::Off); + } + + #[test] + fn serial_dispatch_disables_kernel_cache_policy() { + let config = default_test_config(DispatchMode::Serial); + let cache = cache_safety_interlock(&config); + + assert_eq!(cache.entry_ttl_ms, 0); + assert_eq!(cache.attr_ttl_ms, 0); + assert_eq!(cache.neg_ttl_ms, 0); + assert!(!cache.writeback_cache_enabled); + assert!(!cache.keepcache_enabled); + assert_eq!(cache.readdirplus_mode, ReaddirPlusMode::Off); + } + + #[test] + fn drain_on_release_disables_noopen_and_noflush() { + let _lock = ENV_LOCK.lock().unwrap(); + let _snapshot = EnvSnapshot::capture(FUSE_ENV_KEYS); + std::env::set_var("AGENTFS_DRAIN_ON_RELEASE", "1"); + + let config = FuseConfig::from_env(); + + assert!(config.drain_on_release); + assert!(!config.noopen); + assert!(!config.noflush); + } + + #[test] + fn combined_kill_switches_are_honored() { + let _lock = ENV_LOCK.lock().unwrap(); + let _snapshot = EnvSnapshot::capture(FUSE_ENV_KEYS); + std::env::set_var("AGENTFS_FUSE_NOOPEN", "0"); + std::env::set_var("AGENTFS_FUSE_NOFLUSH", "0"); + std::env::set_var("AGENTFS_FUSE_URING", "0"); + + let config = FuseConfig::from_env(); + + assert!(!config.noopen); + assert!(!config.noflush); + assert!(!config.uring.enabled); + } +} diff --git a/crates/agentfs-fuse/src/adapter/ino_files.rs b/crates/agentfs-fuse/src/adapter/ino_files.rs new file mode 100644 index 00000000..4704e985 --- /dev/null +++ b/crates/agentfs-fuse/src/adapter/ino_files.rs @@ -0,0 +1,38 @@ +use super::write_buffer::{PendingDrain, WriteBuffer}; +use agentfs_core::BoxedFile; + +/// Per-inode file state for the zero-message-open path. With the kernel's +/// `no_open` latched (ENOSYS-OPEN), file ops arrive with `fh=0` and no +/// per-handle state exists: all I/O for an inode shares one resolved +/// `BoxedFile` and one coalescing write buffer. Entries live until FORGET +/// drops the inode (or soft-cap eviction reclaims a clean entry). +pub(super) struct InoFile { + pub(super) file: BoxedFile, + /// Pending writes buffered for coalescing, shared by every open(2) of + /// this inode. Cross-handle write ordering is inherent (one buffer). + pub(super) pending: WriteBuffer, + /// False while the file was resolved for reads only. The first write op + /// re-resolves with `O_RDWR` (triggering overlay copy-up) and replaces + /// `file`, so post-copy-up reads go through the delta layer. + pub(super) write_capable: bool, + /// Resolution stamp consulted by the soft-cap eviction scan. + pub(super) last_used: u64, +} + +impl InoFile { + /// See `OpenFile::take_pending` for the out-of-lock drain contract. + pub(super) fn take_pending(&mut self) -> Option { + if self.pending.is_empty() { + return None; + } + let file = self.file.clone(); + let ranges = self.pending.ranges_for_flush(); + let range_count = ranges.len() as u64; + let byte_count = ranges + .iter() + .map(|range| range.data.len() as u64) + .sum::(); + self.pending.clear(); + Some((file, ranges, range_count, byte_count)) + } +} diff --git a/cli/src/fuse.rs b/crates/agentfs-fuse/src/adapter/mod.rs similarity index 68% rename from cli/src/fuse.rs rename to crates/agentfs-fuse/src/adapter/mod.rs index b4892c45..e0a5a5b5 100644 --- a/cli/src/fuse.rs +++ b/crates/agentfs-fuse/src/adapter/mod.rs @@ -1,21 +1,34 @@ -use crate::fuser::{ +mod cache; +pub(crate) mod config; +mod ino_files; +mod write_buffer; + +use self::cache::{AdapterCaches, CachedDirEntry, KeepCacheFingerprint, NotifyPolicy}; +use self::config::{FuseConfig, FuseKernelCacheConfig, ReaddirPlusMode, UringConfig}; +use self::ino_files::InoFile; +use self::write_buffer::{ + flush_pending_batched_out_of_lock, OpenFile, PendingDrain, WriteBuffer, + FUSE_COALESCE_FLUSH_BYTES, +}; +use crate::transport::{ consts::{ - FOPEN_CACHE_DIR, FOPEN_KEEP_CACHE, FUSE_ASYNC_READ, FUSE_CACHE_SYMLINKS, - FUSE_DO_READDIRPLUS, FUSE_NO_OPENDIR_SUPPORT, FUSE_NO_OPEN_SUPPORT, FUSE_OVER_IO_URING, - FUSE_PARALLEL_DIROPS, FUSE_READDIRPLUS_AUTO, FUSE_WRITEBACK_CACHE, + FOPEN_CACHE_DIR, FOPEN_KEEP_CACHE, FUSE_ASYNC_READ, FUSE_AUTO_INVAL_DATA, + FUSE_CACHE_SYMLINKS, FUSE_DO_READDIRPLUS, FUSE_NO_OPENDIR_SUPPORT, FUSE_NO_OPEN_SUPPORT, + FUSE_OVER_IO_URING, FUSE_PARALLEL_DIROPS, FUSE_READDIRPLUS_AUTO, FUSE_WRITEBACK_CACHE, }, fuse_forget_one, FileAttr, FileType, Filesystem, KernelConfig, MountOption, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyDirectoryPlus, ReplyEmpty, ReplyEntry, ReplyOpen, ReplyStatfs, ReplyWrite, Request, }; -use agentfs_sdk::error::Error as SdkError; -use agentfs_sdk::filesystem::{ - WriteRange, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFSOCK, +use agentfs_core::error::Error as SdkError; +use agentfs_core::fs::{ + KernelCachePolicy, WriteRange, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFSOCK, }; -use agentfs_sdk::{BoxedFile, DirEntry, FileSystem, FsError, Stats, TimeChange}; +use agentfs_core::semantics::Semantics; +use agentfs_core::{BoxedFile, DirEntry, FileSystem, FsError, Stats, TimeChange}; use parking_lot::Mutex; use std::{ - collections::{BTreeMap, HashMap, HashSet}, + collections::{HashMap, HashSet}, ffi::OsStr, path::PathBuf, sync::{ @@ -25,7 +38,6 @@ use std::{ time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; use tokio::runtime::Runtime; -use tracing; /// Convert an SDK error to an errno code for FUSE replies. /// @@ -41,7 +53,9 @@ fn error_to_errno(e: &SdkError) -> i32 { SdkError::ConnectionPoolTimeout => libc::EAGAIN, _ => libc::EIO, }; - tracing::debug!(target: "agentfs_errno", error = %e, errno, "FUSE error reply"); + // Debug capture: SdkError Display no longer carries the source cause + // (source-only chaining), and this trace needs the full detail. + tracing::debug!(target: "agentfs_errno", error = ?e, errno, "FUSE error reply"); errno } @@ -73,210 +87,6 @@ fn maximize_fd_limit() { } } -/// Default kernel TTLs for positive dentries and attributes. 10s lets a whole -/// git-style workload (clone ≈3s + status/diff/fsck) reuse the dentries and -/// attrs established by mutation replies instead of re-LOOKUP/GETATTR storms -/// (warm steady-state reads measured 12.7x native at the old 1s default). -/// Within one mount the kernel is coherent for its own operations regardless -/// of TTL; the TTL only bounds staleness ACROSS concurrent mounts of the same -/// session DB (`agentfs run --session` from another terminal), which now see -/// attribute/namespace changes within 10s. Override with -/// `AGENTFS_FUSE_ENTRY_TTL_MS` / `AGENTFS_FUSE_ATTR_TTL_MS`. -const DEFAULT_FUSE_POSITIVE_TTL_MS: u64 = 10_000; -/// Default kernel TTL for negative dentries. Kept at 1s: a file created by a -/// second mount stays invisible to this mount for the negative TTL, and -/// lookup-miss caching is the most surprising staleness to debug. Override -/// with `AGENTFS_FUSE_NEG_TTL_MS`. -const DEFAULT_FUSE_NEG_TTL_MS: u64 = 1000; -const READDIRPLUS_MODE_OFF: u64 = 0; -const READDIRPLUS_MODE_AUTO: u64 = 1; -const READDIRPLUS_MODE_ALWAYS: u64 = 2; - -/// FUSE kernel cache policy derived once per mount from environment knobs. -#[derive(Debug, Clone)] -struct FuseKernelCacheConfig { - entry_ttl: Duration, - attr_ttl: Duration, - neg_ttl: Duration, - entry_ttl_ms: u64, - attr_ttl_ms: u64, - neg_ttl_ms: u64, - writeback_cache_enabled: bool, - keepcache_enabled: bool, - readdirplus_mode: ReaddirPlusMode, -} - -impl FuseKernelCacheConfig { - fn from_env() -> Self { - let entry_ttl_ms = - env_duration_ms("AGENTFS_FUSE_ENTRY_TTL_MS", DEFAULT_FUSE_POSITIVE_TTL_MS); - let attr_ttl_ms = env_duration_ms("AGENTFS_FUSE_ATTR_TTL_MS", DEFAULT_FUSE_POSITIVE_TTL_MS); - let neg_ttl_ms = env_duration_ms("AGENTFS_FUSE_NEG_TTL_MS", DEFAULT_FUSE_NEG_TTL_MS); - - // Kernel cache safety requires non-serial workers: we need a worker thread - // distinct from the session loop to send FUSE_NOTIFY_INVAL_* without - // blocking the request reader. Serial mode keeps reply+notify on the same - // thread which deadlocks per cli/src/fuser/deferred_notify.rs. - // - // Whether AGENTFS_FUSE_SYNC_INVAL is on does NOT affect safety here: - // - On (sync): worker writev's notify directly. Risk: kernel may block - // the worker's writev waiting for an inline FUSE_FORGET that the - // session thread cannot deliver if its lane queue is full. This - // reproduces under git clone on Linux 6+ kernels. - // - Off (deferred, the default): notify is enqueued to the dedicated - // notify thread that owns its own writev fd. The notify thread is - // never blocked by the dispatch path, so the kernel-side FORGET - // round-trip drains independently. Cache coherency is bounded by - // the few-microsecond latency between mutation reply and notify - // delivery, which is well within the entry/attr TTL window. - // - // So: safe_kernel_cache only requires non-serial workers, and the - // sync_invalidation env var is treated as an unsafe opt-in. - let workers_not_serial = fuse_workers_not_serial_from_env(); - let safe_kernel_cache = workers_not_serial; - let (entry_ttl_ms, attr_ttl_ms, neg_ttl_ms) = if safe_kernel_cache { - (entry_ttl_ms, attr_ttl_ms, neg_ttl_ms) - } else { - if entry_ttl_ms != 0 || attr_ttl_ms != 0 || neg_ttl_ms != 0 { - tracing::warn!( - "Refusing nonzero FUSE TTLs: kernel entry/attr/negative TTLs require non-serial AGENTFS_FUSE_WORKERS" - ); - } - (0, 0, 0) - }; - - let writeback_requested = env_flag_default("AGENTFS_FUSE_WRITEBACK", true); - let writeback_cache_enabled = writeback_requested && safe_kernel_cache; - if writeback_requested && !writeback_cache_enabled { - tracing::warn!( - "Refusing FUSE writeback cache: AGENTFS_FUSE_WRITEBACK requires non-serial AGENTFS_FUSE_WORKERS" - ); - } - - let keepcache_requested = env_flag_default("AGENTFS_FUSE_KEEPCACHE", true); - let keepcache_enabled = keepcache_requested && safe_kernel_cache; - if keepcache_requested && !keepcache_enabled { - tracing::warn!( - "Refusing FOPEN_KEEP_CACHE: AGENTFS_FUSE_KEEPCACHE requires non-serial AGENTFS_FUSE_WORKERS" - ); - } - let readdirplus_mode = if safe_kernel_cache { - readdirplus_mode_from_env() - } else { - tracing::warn!( - "Refusing FUSE readdirplus: readdirplus requires non-serial AGENTFS_FUSE_WORKERS" - ); - ReaddirPlusMode::Off - }; - - Self { - entry_ttl: Duration::from_millis(entry_ttl_ms), - attr_ttl: Duration::from_millis(attr_ttl_ms), - neg_ttl: Duration::from_millis(neg_ttl_ms), - entry_ttl_ms, - attr_ttl_ms, - neg_ttl_ms, - writeback_cache_enabled, - keepcache_enabled, - readdirplus_mode, - } - } - - fn record_profile(&self) { - agentfs_sdk::profiling::set_fuse_ttl_ms( - self.entry_ttl_ms, - self.attr_ttl_ms, - self.neg_ttl_ms, - ); - agentfs_sdk::profiling::set_fuse_keepcache_enabled(self.keepcache_enabled); - agentfs_sdk::profiling::set_fuse_readdirplus_mode(self.readdirplus_mode.profile_value()); - } -} - -/// Guards `FOPEN_KEEP_CACHE` grants against serving stale kernel pages. -/// -/// Non-sticky (default): a mutation drops the stored fingerprint and the next -/// read-only open revalidates against fresh stats. This is sound because -/// every mutation path is kernel-originated — the kernel's own pages stay -/// coherent for its own writes, and adapter-notified invalidations purge them -/// — so a fingerprint match at open time means the pages the kernel kept are -/// current. Non-kernel divergence (direct SDK writers, other mounts) changes -/// mtime/ctime/size and fails the fingerprint check, same as the host-file -/// model. `AGENTFS_FUSE_STICKY_KEEPCACHE_DROP=1` restores the old sticky -/// behaviour where any mutation permanently revokes eligibility per mount. -#[derive(Debug, Default)] -struct KeepCacheDriftGuard { - sticky: bool, - dropped: HashSet, - fingerprints: HashMap, -} - -#[derive(Debug, Clone, Eq, PartialEq)] -struct KeepCacheFingerprint { - mode: u32, - size: i64, - mtime: i64, - mtime_nsec: u32, - ctime: i64, - ctime_nsec: u32, - rdev: u64, -} - -impl KeepCacheFingerprint { - fn from_stats(stats: &Stats) -> Self { - Self { - mode: stats.mode, - size: stats.size, - mtime: stats.mtime, - mtime_nsec: stats.mtime_nsec, - ctime: stats.ctime, - ctime_nsec: stats.ctime_nsec, - rdev: stats.rdev, - } - } -} - -impl KeepCacheDriftGuard { - fn new(sticky: bool) -> Self { - Self { - sticky, - ..Self::default() - } - } - - fn allows(&self, ino: u64, fingerprint: &KeepCacheFingerprint) -> bool { - !self.dropped.contains(&ino) - && self - .fingerprints - .get(&ino) - .map(|existing| existing == fingerprint) - .unwrap_or(true) - } - - fn mark_eligible(&mut self, ino: u64, fingerprint: KeepCacheFingerprint) { - if !self.dropped.contains(&ino) { - self.fingerprints.insert(ino, fingerprint); - } - } - - fn drop_eligibility(&mut self, ino: u64) -> bool { - let had_fingerprint = self.fingerprints.remove(&ino).is_some(); - if self.sticky { - self.dropped.insert(ino) || had_fingerprint - } else { - had_fingerprint - } - } -} - -/// Kernel readdirplus policy. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -enum ReaddirPlusMode { - Off, - Auto, - Always, -} - /// Options for mounting an agent filesystem via FUSE. #[derive(Debug, Clone)] pub struct FuseMountOptions { @@ -297,249 +107,6 @@ pub struct FuseMountOptions { pub gid: Option, } -/// Threshold at which the FUSE-layer per-fh write coalescer flushes its -/// accumulated ranges down to the SDK. Picked at 4x the chunk size so a single -/// flushed call covers a few SQLite chunks and the AsyncMutex acquisition in -/// the SDK write batcher is amortised across many FUSE_WRITE requests for the -/// same handle. Smaller writes (the common git-clone case) accumulate in this -/// buffer until `flush` / `release` arrives and only then hit the SDK. -const FUSE_COALESCE_FLUSH_BYTES: usize = 256 * 1024; - -/// Tracks an open file handle -struct OpenFile { - /// Inode associated with this FUSE file handle. - ino: u64, - /// The file handle from the filesystem layer. - file: BoxedFile, - /// Pending writes buffered for coalescing before reaching the filesystem layer. - pending: WriteBuffer, -} - -impl OpenFile { - fn new(ino: u64, file: BoxedFile) -> Self { - Self { - ino, - file, - pending: WriteBuffer::default(), - } - } - - #[cfg(test)] - fn buffer_write(&mut self, offset: u64, data: &[u8]) -> Result<(), i32> { - self.pending.write(offset, data)?; - Ok(()) - } - - /// Coalesce a single FUSE write into the per-fh pending buffer. Returns - /// `true` if the cumulative buffer size has reached the flush threshold - /// and the caller should drain it before replying to the kernel. - fn buffer_fuse_write(&mut self, offset: u64, data: &[u8]) -> Result { - self.pending.write(offset, data)?; - Ok(self.pending.bytes >= FUSE_COALESCE_FLUSH_BYTES) - } - - /// Drain the per-fh pending buffer into a `(file, ranges, range_count, - /// byte_count)` tuple so the caller can release the surrounding - /// `open_files` lock before issuing the async `pwrite_ranges*` call. The - /// hot write path MUST NOT hold the parking_lot `open_files` mutex across - /// `runtime.block_on(...)`: doing so serializes every other FUSE handler - /// behind one fh's SQLite commit and was the source of a 2x checkout - /// regression observed in the first Tier Two benchmark pass. - fn take_pending(&mut self) -> Option { - if self.pending.is_empty() { - return None; - } - let file = self.file.clone(); - let ranges = self.pending.ranges_for_flush(); - let range_count = ranges.len() as u64; - let byte_count = ranges - .iter() - .map(|range| range.data.len() as u64) - .sum::(); - self.pending.clear(); - Some((file, ranges, range_count, byte_count)) - } - - /// Synchronous flush via the non-batched pwrite API. Production code uses - /// `take_pending` + `flush_pending_batched_out_of_lock` instead; this - /// remains as a test-only convenience so the OpenFile unit tests stay - /// readable. - #[cfg(test)] - fn flush_pending(&mut self, runtime: &Runtime) -> Result<(), SdkError> { - let Some((file, ranges, range_count, byte_count)) = self.take_pending() else { - return Ok(()); - }; - - runtime.block_on(async move { file.pwrite_ranges(ranges).await })?; - agentfs_sdk::profiling::record_fuse_flush(range_count, byte_count); - Ok(()) - } -} - -/// `(file, ranges, range_count, byte_count)` drained from a pending -/// `WriteBuffer`; flushed out-of-lock via `flush_pending_batched_out_of_lock`. -type PendingDrain = (BoxedFile, Vec, u64, u64); - -/// Per-inode file state for the zero-message-open path. With the kernel's -/// `no_open` latched (ENOSYS-OPEN), file ops arrive with `fh=0` and no -/// per-handle state exists: all I/O for an inode shares one resolved -/// `BoxedFile` and one coalescing write buffer. Entries live until FORGET -/// drops the inode (or soft-cap eviction reclaims a clean entry). -struct InoFile { - file: BoxedFile, - /// Pending writes buffered for coalescing, shared by every open(2) of - /// this inode. Cross-handle write ordering is inherent (one buffer). - pending: WriteBuffer, - /// False while the file was resolved for reads only. The first write op - /// re-resolves with `O_RDWR` (triggering overlay copy-up) and replaces - /// `file`, so post-copy-up reads go through the delta layer. - write_capable: bool, - /// Resolution stamp consulted by the soft-cap eviction scan. - last_used: u64, -} - -impl InoFile { - /// See `OpenFile::take_pending` for the out-of-lock drain contract. - fn take_pending(&mut self) -> Option { - if self.pending.is_empty() { - return None; - } - let file = self.file.clone(); - let ranges = self.pending.ranges_for_flush(); - let range_count = ranges.len() as u64; - let byte_count = ranges - .iter() - .map(|range| range.data.len() as u64) - .sum::(); - self.pending.clear(); - Some((file, ranges, range_count, byte_count)) - } -} - -/// Flush a `(file, ranges, range_count, byte_count)` tuple produced by -/// `OpenFile::take_pending()` via the SDK write batcher (so the coalesced -/// ranges enter the cross-inode batched-commit path). Called by the FUSE -/// write / flush / release handlers AFTER they have released the -/// `open_files` parking_lot mutex. -fn flush_pending_batched_out_of_lock( - runtime: &Runtime, - drain: PendingDrain, -) -> Result<(), SdkError> { - let (file, ranges, range_count, byte_count) = drain; - runtime.block_on(async move { file.pwrite_ranges_batched(ranges).await })?; - agentfs_sdk::profiling::record_fuse_flush(range_count, byte_count); - Ok(()) -} - -/// Pending write ranges for one open FUSE file handle. -/// -/// Ranges are keyed by start offset and kept non-overlapping. Adjacent and -/// overlapping writes are merged eagerly so common sequential writes become one -/// filesystem-layer `pwrite` when the handle is flushed. -#[derive(Default)] -struct WriteBuffer { - ranges: BTreeMap>, - bytes: usize, -} - -impl WriteBuffer { - fn is_empty(&self) -> bool { - self.ranges.is_empty() - } - - #[cfg(test)] - fn bytes(&self) -> usize { - self.bytes - } - - fn clear(&mut self) { - self.ranges.clear(); - self.bytes = 0; - } - - fn ranges_for_flush(&self) -> Vec { - self.ranges - .iter() - .map(|(&offset, data)| WriteRange { - offset, - data: data.clone(), - }) - .collect() - } - - fn write(&mut self, offset: u64, data: &[u8]) -> Result<(), i32> { - if data.is_empty() { - return Ok(()); - } - - let data_len = u64::try_from(data.len()).map_err(|_| libc::EINVAL)?; - let write_start = offset; - let write_end = offset.checked_add(data_len).ok_or(libc::EINVAL)?; - let mut start = write_start; - let mut end = write_end; - let mut existing_ranges = Vec::new(); - - if let Some((&prev_start, prev_data)) = self.ranges.range(..=write_start).next_back() { - let prev_end = prev_start - .checked_add(prev_data.len() as u64) - .ok_or(libc::EINVAL)?; - - if prev_end >= write_start { - let prev_data = prev_data.clone(); - self.ranges.remove(&prev_start); - self.bytes -= prev_data.len(); - - start = prev_start; - end = end.max(prev_end); - existing_ranges.push((prev_start, prev_data)); - } - } - - loop { - let next = self - .ranges - .range(start..) - .next() - .map(|(&next_start, next_data)| (next_start, next_data.clone())); - - let Some((next_start, next_data)) = next else { - break; - }; - - if next_start > end { - break; - } - - let next_end = next_start - .checked_add(next_data.len() as u64) - .ok_or(libc::EINVAL)?; - self.ranges.remove(&next_start); - self.bytes -= next_data.len(); - - end = end.max(next_end); - existing_ranges.push((next_start, next_data)); - } - - let mut merged = vec![0; (end - start) as usize]; - for (range_start, range_data) in existing_ranges { - let range_offset = (range_start - start) as usize; - merged[range_offset..range_offset + range_data.len()].copy_from_slice(&range_data); - } - - let write_offset = (write_start - start) as usize; - merged[write_offset..write_offset + data.len()].copy_from_slice(data); - - self.bytes += merged.len(); - self.ranges.insert(start, merged); - Ok(()) - } -} - -struct CachedDirEntry { - name: String, - attr: FileAttr, -} - #[cfg(debug_assertions)] thread_local! { static MUTATION_INVALIDATIONS: std::cell::Cell = const { std::cell::Cell::new(0) }; @@ -598,25 +165,16 @@ impl MutationAudit { struct AgentFSFuse { fs: Arc, + semantics: Semantics, runtime: Runtime, /// Env-backed kernel cache safety configuration for this mount. cache_config: FuseKernelCacheConfig, + /// Env-backed FUSE-over-io_uring settings for this mount. + uring: UringConfig, /// Maps file handle -> open file state open_files: Arc>>, - /// Caches fully materialized directory entries across FUSE readdir offset calls. - dir_entries_cache: Arc>>>>, - /// Caches attributes discovered by lookup/readdir_plus for read-heavy traversals. - attr_cache: Arc>>, - /// Caches positive parent/name lookups discovered by lookup/readdir_plus. - entry_cache: Arc>>, - /// Caches negative parent/name lookups; exact namespace mutations remove or update keys. - negative_entry_cache: Arc>>, - /// Drops FOPEN_KEEP_CACHE eligibility after mutations that can stale kernel pages. - keepcache_drift_guard: Arc>, - /// Serializes cacheable FUSE replies against mutation invalidations. - cache_reply_lock: Arc>, - /// Monotonic epoch bumped whenever a mutation invalidates cached namespace or attrs. - cache_epoch: AtomicU64, + /// Adapter cache state and the epoch/reply-lock coherence protocol. + caches: AdapterCaches, /// Next file handle to allocate next_fh: AtomicU64, /// Whether kernel cache invalidations are sent synchronously before replies. @@ -699,8 +257,6 @@ struct AgentFSFuse { /// readdirplus) check this before scanning `open_files`, keeping the hot /// no-writes-in-flight case at a single atomic load. pending_dirty_handles: AtomicUsize, - /// Emits a profiling summary when the FUSE session object is dropped. - _profile_report: Arc, /// Whether FUSE writeback mode is enabled for this mount. writeback_enabled: bool, } @@ -725,7 +281,8 @@ impl Filesystem for AgentFSFuse { // FUSE_NO_OPENDIR_SUPPORT skips the OPENDIR round trip, but granting // FOPEN_CACHE_DIR requires replying to OPENDIR; one round trip per // opendir(3) buys kernel-cached getdents for every warm re-listing. - let mut capabilities = FUSE_ASYNC_READ | FUSE_PARALLEL_DIROPS | FUSE_CACHE_SYMLINKS; + let mut capabilities = + FUSE_ASYNC_READ | FUSE_AUTO_INVAL_DATA | FUSE_PARALLEL_DIROPS | FUSE_CACHE_SYMLINKS; if !self.cache_dir_enabled { capabilities |= FUSE_NO_OPENDIR_SUPPORT; } @@ -738,12 +295,12 @@ impl Filesystem for AgentFSFuse { // clamp keeps per-entry ring payload buffers at 1 MiB (the kernel // caps single WRITEs at max_pages = 256 pages anyway, so >1 MiB // writes never materialize on Linux). - if crate::fuser::uring::uring_enabled() { - if crate::fuser::uring::probe_ring_setup() + if self.uring.enabled { + if crate::transport::uring::probe_ring_setup() && config.add_capabilities(FUSE_OVER_IO_URING).is_ok() { - let _ = config.set_max_write(crate::fuser::uring::URING_MAX_WRITE); - let _ = config.set_max_readahead(crate::fuser::uring::URING_MAX_WRITE); + let _ = config.set_max_write(crate::transport::uring::URING_MAX_WRITE); + let _ = config.set_max_readahead(crate::transport::uring::URING_MAX_WRITE); tracing::info!("advertising FUSE_OVER_IO_URING"); } else { tracing::debug!("fuse-over-io_uring unavailable; using legacy channel"); @@ -769,10 +326,13 @@ impl Filesystem for AgentFSFuse { fn destroy(&self) { tracing::debug!("FUSE::destroy"); if let Err(e) = self.flush_all_pending() { - tracing::warn!("FUSE::destroy failed to flush pending writes: {}", e); + tracing::warn!(error = ?e, "FUSE::destroy failed to flush pending writes"); + } + if let Err(e) = self.commit_barrier(None) { + tracing::warn!(error = ?e, "FUSE::destroy failed to commit pending writes"); } if let Err(e) = self.finalize_filesystem() { - tracing::warn!("FUSE::destroy failed to finalize filesystem: {}", e); + tracing::warn!(error = ?e, "FUSE::destroy failed to finalize filesystem"); } } @@ -784,7 +344,7 @@ impl Filesystem for AgentFSFuse { /// /// Resolves `name` under the directory identified by `parent` inode. fn lookup(&self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { - agentfs_sdk::profiling::record_fuse_lookup(); + crate::telemetry::record_fuse_lookup(); tracing::debug!("FUSE::lookup: parent={}, name={:?}", parent, name); let Some(name_str) = name.to_str() else { @@ -793,20 +353,24 @@ impl Filesystem for AgentFSFuse { }; let cache_epoch = self.cache_epoch(); - if let Some(stats) = self - .entry_cache - .lock() - .get(&(parent, name_str.to_string())) - .cloned() - { + let cached_entry = if self.attrs_cacheable() { + self.caches + .entries() + .lock() + .get(&(parent, name_str.to_string())) + .cloned() + } else { + None + }; + if let Some(stats) = cached_entry { let fs = self.fs.clone(); let retained = self .runtime .block_on(async move { fs.retain_lookup(stats.ino, 1).await }) .is_ok(); - let cache_reply = self.cache_reply_lock.try_lock(); - if retained && cache_reply.is_some() && !self.cache_epoch_changed(cache_epoch) { - agentfs_sdk::profiling::record_fuse_adapter_entry_hit(); + let cache_reply = self.caches.try_reply_guard(cache_epoch); + if retained && cache_reply.is_some() { + crate::telemetry::record_fuse_adapter_entry_hit(); let attr = fillattr(&stats); reply.entry_with_ttls( &self.cache_config.entry_ttl, @@ -826,23 +390,24 @@ impl Filesystem for AgentFSFuse { let cache_epoch = self.cache_epoch(); if self - .negative_entry_cache + .caches + .negative_entries() .lock() .contains_key(&(parent, name_str.to_string())) { - let cache_reply = self.cache_reply_lock.try_lock(); - if cache_reply.is_some() && !self.cache_epoch_changed(cache_epoch) { - agentfs_sdk::profiling::record_negative_cache_hit(); - agentfs_sdk::profiling::record_fuse_adapter_negative_hit(); + let cache_reply = self.caches.try_reply_guard(cache_epoch); + if cache_reply.is_some() { + crate::telemetry::record_negative_cache_hit(); + crate::telemetry::record_fuse_adapter_negative_hit(); self.reply_negative_entry(reply); return; } } - agentfs_sdk::profiling::record_negative_cache_miss(); - agentfs_sdk::profiling::record_fuse_adapter_negative_miss(); + crate::telemetry::record_negative_cache_miss(); + crate::telemetry::record_fuse_adapter_negative_miss(); // Neither the positive entry cache nor the negative cache satisfied this // lookup; the request falls through to the backend. - agentfs_sdk::profiling::record_fuse_adapter_entry_miss(); + crate::telemetry::record_fuse_adapter_entry_miss(); let mut stable = false; let mut stable_epoch = 0; @@ -862,8 +427,8 @@ impl Filesystem for AgentFSFuse { } } let result = result.expect("lookup loop always runs"); - let cache_reply = self.cache_reply_lock.try_lock(); - stable = stable && cache_reply.is_some() && !self.cache_epoch_changed(stable_epoch); + let cache_reply = self.caches.try_reply_guard(stable_epoch); + stable = stable && cache_reply.is_some(); match result { Ok(Some(stats)) => { @@ -889,17 +454,32 @@ impl Filesystem for AgentFSFuse { return; } }; - if stable { + let external_drift = self.external_drift_sensitive(stats.ino as u64); + let drift_detected = + external_drift && self.external_drift_detected(stats.ino as u64, &stats); + if drift_detected { + reply.error(libc::EIO); + crate::telemetry::record_base_fast_stale_rejection(); + return; + } + if external_drift { + self.caches.set_external_read_fingerprint( + stats.ino as u64, + KeepCacheFingerprint::from_stats(&stats), + ); + } + if stable && !external_drift { self.cache_entry(parent, name_str, &stats); } let attr = fillattr(&stats); + let cacheable = stable && !external_drift; reply.entry_with_ttls( - if stable { + if cacheable { &self.cache_config.entry_ttl } else { &Duration::ZERO }, - if stable { + if cacheable { &self.cache_config.attr_ttl } else { &Duration::ZERO @@ -923,7 +503,7 @@ impl Filesystem for AgentFSFuse { /// Returns metadata (size, permissions, timestamps, etc.) for the file or /// directory identified by `ino`. Root inode (1) is handled specially. fn getattr(&self, _req: &Request, ino: u64, _fh: Option, reply: ReplyAttr) { - agentfs_sdk::profiling::record_fuse_getattr(); + crate::telemetry::record_fuse_getattr(); tracing::debug!("FUSE::getattr: ino={}", ino); if let Err(e) = self.flush_pending_inode(ino) { @@ -932,15 +512,20 @@ impl Filesystem for AgentFSFuse { } let cache_epoch = self.cache_epoch(); - if let Some(stats) = self.attr_cache.lock().get(&ino).cloned() { - let cache_reply = self.cache_reply_lock.try_lock(); - if cache_reply.is_some() && !self.cache_epoch_changed(cache_epoch) { - agentfs_sdk::profiling::record_fuse_adapter_attr_hit(); + let cached_attr = if self.attrs_cacheable() { + self.caches.attrs().lock().get(&ino).cloned() + } else { + None + }; + if let Some(stats) = cached_attr { + let cache_reply = self.caches.try_reply_guard(cache_epoch); + if cache_reply.is_some() { + crate::telemetry::record_fuse_adapter_attr_hit(); reply.attr(&self.cache_config.attr_ttl, &fillattr(&stats)); return; } } - agentfs_sdk::profiling::record_fuse_adapter_attr_miss(); + crate::telemetry::record_fuse_adapter_attr_miss(); let mut stable = false; let mut stable_epoch = 0; @@ -959,16 +544,30 @@ impl Filesystem for AgentFSFuse { } } let result = result.expect("getattr loop always runs"); - let cache_reply = self.cache_reply_lock.try_lock(); - stable = stable && cache_reply.is_some() && !self.cache_epoch_changed(stable_epoch); + let cache_reply = self.caches.try_reply_guard(stable_epoch); + stable = stable && cache_reply.is_some(); match result { Ok(Some(stats)) => { - if stable { + let external_drift = self.external_drift_sensitive(ino); + let drift_detected = external_drift && self.external_drift_detected(ino, &stats); + if drift_detected { + reply.error(libc::EIO); + crate::telemetry::record_base_fast_stale_rejection(); + return; + } + if external_drift { + self.caches.set_external_read_fingerprint( + ino, + KeepCacheFingerprint::from_stats(&stats), + ); + } + let cacheable = stable && !external_drift; + if cacheable && self.attrs_cacheable() { self.cache_attr(&stats); } reply.attr( - if stable { + if cacheable { &self.cache_config.attr_ttl } else { &Duration::ZERO @@ -1012,14 +611,10 @@ impl Filesystem for AgentFSFuse { uid: Option, gid: Option, size: Option, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, _ctime: Option, fh: Option, - _crtime: Option, - _chgtime: Option, - _bkuptime: Option, - _flags: Option, reply: ReplyAttr, ) { tracing::debug!( @@ -1115,19 +710,19 @@ impl Filesystem for AgentFSFuse { // Handle atime/mtime changes (utimensat) if atime.is_some() || mtime.is_some() { let new_atime = match atime { - Some(crate::fuser::TimeOrNow::SpecificTime(t)) => { + Some(crate::transport::TimeOrNow::SpecificTime(t)) => { let dur = t.duration_since(UNIX_EPOCH).unwrap_or_default(); TimeChange::Set(dur.as_secs() as i64, dur.subsec_nanos()) } - Some(crate::fuser::TimeOrNow::Now) => TimeChange::Now, + Some(crate::transport::TimeOrNow::Now) => TimeChange::Now, None => TimeChange::Omit, }; let new_mtime = match mtime { - Some(crate::fuser::TimeOrNow::SpecificTime(t)) => { + Some(crate::transport::TimeOrNow::SpecificTime(t)) => { let dur = t.duration_since(UNIX_EPOCH).unwrap_or_default(); TimeChange::Set(dur.as_secs() as i64, dur.subsec_nanos()) } - Some(crate::fuser::TimeOrNow::Now) => TimeChange::Now, + Some(crate::transport::TimeOrNow::Now) => TimeChange::Now, None => TimeChange::Omit, }; let fs = self.fs.clone(); @@ -1174,7 +769,7 @@ impl Filesystem for AgentFSFuse { /// Uses readdir_plus to fetch entries with stats in a single query, /// avoiding N+1 database queries. fn readdir(&self, _req: &Request, ino: u64, _fh: u64, offset: i64, mut reply: ReplyDirectory) { - agentfs_sdk::profiling::record_fuse_readdir(); + crate::telemetry::record_fuse_readdir(); tracing::debug!("FUSE::readdir: ino={}, offset={}", ino, offset); let all_entries = match self.cached_readdir_entries(ino) { @@ -1206,7 +801,7 @@ impl Filesystem for AgentFSFuse { offset: i64, mut reply: ReplyDirectoryPlus, ) { - agentfs_sdk::profiling::record_fuse_readdir_plus(); + crate::telemetry::record_fuse_readdir_plus(); tracing::debug!("FUSE::readdirplus: ino={}, offset={}", ino, offset); let (all_entries, stable, stable_epoch) = match self.cached_readdir_entries(ino) { @@ -1217,19 +812,20 @@ impl Filesystem for AgentFSFuse { } }; - let cache_reply = self.cache_reply_lock.try_lock(); - let stable = stable && cache_reply.is_some() && !self.cache_epoch_changed(stable_epoch); + let cache_reply = self.caches.try_reply_guard(stable_epoch); + let stable = stable && cache_reply.is_some(); for (i, entry) in all_entries.iter().enumerate().skip(readdir_start(offset)) { + let entry_cacheable = stable && !self.external_drift_sensitive(entry.attr.ino); if reply.add_with_ttls( entry.attr.ino, (i + 1) as i64, &entry.name, - if stable { + if entry_cacheable { &self.cache_config.entry_ttl } else { &Duration::ZERO }, - if stable { + if entry_cacheable { &self.cache_config.attr_ttl } else { &Duration::ZERO @@ -1673,7 +1269,7 @@ impl Filesystem for AgentFSFuse { /// /// Allocates a file handle and opens the file in the filesystem layer. fn open(&self, req: &Request, ino: u64, flags: i32, reply: ReplyOpen) { - agentfs_sdk::profiling::record_fuse_open(); + crate::telemetry::record_fuse_open(); tracing::debug!("FUSE::open: ino={}, flags={}", ino, flags); if self.noopen_active.load(Ordering::Acquire) { @@ -1686,7 +1282,7 @@ impl Filesystem for AgentFSFuse { // writeback-coherent, truncates arrive as SETATTR (we never // advertise FUSE_ATOMIC_O_TRUNC), and external divergence is // bounded by the attr TTLs. - agentfs_sdk::profiling::record_fuse_noopen_enosys_reply(); + crate::telemetry::record_fuse_noopen_enosys_reply(); reply.error(libc::ENOSYS); return; } @@ -1709,11 +1305,11 @@ impl Filesystem for AgentFSFuse { // grant exactly as it does for SDK-derived stats. let cached_fingerprint = if check_keep_cache && self.keepcache_delta_enabled { let epoch = self.cache_epoch(); - let stats = self.attr_cache.lock().get(&ino).cloned(); + let stats = self.caches.attrs().lock().get(&ino).cloned(); match stats { Some(stats) if stats.is_file() => { - let cache_reply = self.cache_reply_lock.try_lock(); - if cache_reply.is_some() && !self.cache_epoch_changed(epoch) { + let cache_reply = self.caches.try_reply_guard(epoch); + if cache_reply.is_some() { Some(KeepCacheFingerprint::from_stats(&stats)) } else { None @@ -1752,7 +1348,7 @@ impl Filesystem for AgentFSFuse { if self.keepcache_allows(ino, fingerprint) { true } else { - agentfs_sdk::profiling::record_base_fast_stale_rejection(); + crate::telemetry::record_base_fast_stale_rejection(); false } }) @@ -1764,10 +1360,10 @@ impl Filesystem for AgentFSFuse { ino, keep_cache_fingerprint.expect("checked before enabling keep-cache"), ); - agentfs_sdk::profiling::record_base_fast_open_eligible(); - agentfs_sdk::profiling::record_base_fast_open_keep_cache(); + crate::telemetry::record_base_fast_open_eligible(); + crate::telemetry::record_base_fast_open_keep_cache(); } else { - agentfs_sdk::profiling::record_base_fast_open_rejected(); + crate::telemetry::record_base_fast_open_rejected(); } if write_open { self.invalidate_inode_cache_self(req, ino); @@ -1799,7 +1395,7 @@ impl Filesystem for AgentFSFuse { /// Reads data using the file handle. fn read( &self, - _req: &Request, + req: &Request, ino: u64, fh: u64, offset: i64, @@ -1808,12 +1404,13 @@ impl Filesystem for AgentFSFuse { _lock: Option, reply: ReplyData, ) { - agentfs_sdk::profiling::record_fuse_read(); + crate::telemetry::record_fuse_read(); tracing::debug!("FUSE::read: fh={}, offset={}, size={}", fh, offset, size); if offset < 0 { reply.error(libc::EINVAL); return; } + let external_drift_read = self.external_drift_sensitive(ino); let file = { let open_files = self.open_files.lock(); @@ -1841,7 +1438,17 @@ impl Filesystem for AgentFSFuse { .block_on(async move { file.pread(offset as u64, size as u64).await }); match result { - Ok(data) => reply.data(&data), + Ok(data) => { + let notifier = external_drift_read.then(|| req.deferred_notifier().clone()); + if external_drift_read { + self.caches.mark_external_read_seen(ino); + } + reply.data(&data); + if let Some(notifier) = notifier { + notifier.inval_inode(ino, -1, 0); + crate::telemetry::record_base_fast_inode_invalidation(); + } + } Err(e) => reply.error(error_to_errno(&e)), } } @@ -1873,7 +1480,7 @@ impl Filesystem for AgentFSFuse { } let data_len = data.len(); - agentfs_sdk::profiling::record_fuse_write(data_len as u64); + crate::telemetry::record_fuse_write(data_len as u64); if let Err(e) = self.flush_pending_inode_except(ino, fh) { reply.error(error_to_errno(&e)); return; @@ -2058,7 +1665,7 @@ impl Filesystem for AgentFSFuse { // or the pending-tail guards on attr-bearing paths. On // drain errors the real errno is replied instead, which // leaves FLUSH enabled and close() still reporting them. - agentfs_sdk::profiling::record_fuse_noflush_enosys_reply(); + crate::telemetry::record_fuse_noflush_enosys_reply(); reply.error(libc::ENOSYS); } else { reply.ok(); @@ -2068,17 +1675,14 @@ impl Filesystem for AgentFSFuse { } } - /// Synchronizes file data to persistent storage using the file handle. - /// - /// This now uses the file handle's fsync which knows which layer(s) the - /// file exists in, avoiding errors when a file only exists in one layer. + /// Synchronizes file data through the shared inode commit barrier. fn fsync(&self, _req: &Request, ino: u64, fh: u64, _datasync: bool, reply: ReplyEmpty) { tracing::debug!("FUSE::fsync: fh={}", fh); let file = { let open_files = self.open_files.lock(); open_files.get(&fh).map(|open_file| open_file.file.clone()) }; - let file = match file { + let _file = match file { Some(file) => file, None => match self.resolve_ino_file(ino, false) { Ok(file) => file, @@ -2094,7 +1698,7 @@ impl Filesystem for AgentFSFuse { return; } - let result = self.runtime.block_on(async move { file.fsync().await }); + let result = self.commit_barrier(Some(ino)); match result { Ok(()) => reply.ok(), @@ -2115,7 +1719,7 @@ impl Filesystem for AgentFSFuse { _flush: bool, reply: ReplyEmpty, ) { - agentfs_sdk::profiling::record_fuse_release(); + crate::telemetry::record_fuse_release(); tracing::debug!("FUSE::release: fh={}", fh); // Deferred-drain default: move this handle's buffered writes into the // SDK batcher overlay, but do NOT force a SQLite commit on close. The @@ -2216,9 +1820,8 @@ impl Filesystem for AgentFSFuse { if drain_on_forget { if let Err(error) = fs.drain_inode_writes(ino as i64).await { tracing::warn!( - "FUSE::forget failed to drain batched writes for inode {}: {}", - ino, - error + ?error, + "FUSE::forget failed to drain batched writes for inode {ino}" ); } } @@ -2244,9 +1847,8 @@ impl Filesystem for AgentFSFuse { if drain_on_forget { if let Err(error) = fs.drain_inode_writes(ino).await { tracing::warn!( - "FUSE::batch_forget failed to drain batched writes for inode {}: {}", - ino, - error + ?error, + "FUSE::batch_forget failed to drain batched writes for inode {ino}" ); } } @@ -2259,10 +1861,13 @@ impl Filesystem for AgentFSFuse { impl Drop for AgentFSFuse { fn drop(&mut self) { if let Err(e) = self.flush_all_pending() { - tracing::warn!("FUSE drop failed to flush pending writes: {}", e); + tracing::warn!(error = ?e, "FUSE drop failed to flush pending writes"); + } + if let Err(e) = self.commit_barrier(None) { + tracing::warn!(error = ?e, "FUSE drop failed to commit pending writes"); } if let Err(e) = self.finalize_filesystem() { - tracing::warn!("FUSE drop failed to finalize filesystem: {}", e); + tracing::warn!(error = ?e, "FUSE drop failed to finalize filesystem"); } } } @@ -2356,15 +1961,11 @@ impl AgentFSFuse { } fn cache_epoch(&self) -> u64 { - self.cache_epoch.load(Ordering::Acquire) + self.caches.epoch() } fn cache_epoch_changed(&self, epoch: u64) -> bool { - self.cache_epoch.load(Ordering::Acquire) != epoch - } - - fn bump_cache_epoch(&self) { - self.cache_epoch.fetch_add(1, Ordering::AcqRel); + self.caches.epoch_changed(epoch) } fn reply_negative_entry(&self, reply: ReplyEntry) { @@ -2383,6 +1984,25 @@ impl AgentFSFuse { } } + fn attrs_cacheable(&self) -> bool { + !self.cache_config.attr_ttl.is_zero() + } + + fn kernel_cache_policy(&self, ino: u64) -> KernelCachePolicy { + self.fs.kernel_cache_policy(ino as i64) + } + + fn external_drift_sensitive(&self, ino: u64) -> bool { + self.kernel_cache_policy(ino).has_external_drift() + } + + fn external_drift_detected(&self, ino: u64, stats: &Stats) -> bool { + self.caches.external_read_was_seen(ino) + && self + .caches + .external_read_drifted(ino, KeepCacheFingerprint::from_stats(stats)) + } + fn has_pending_write_for_inode(&self, ino: u64) -> bool { self.open_files .lock() @@ -2417,14 +2037,14 @@ impl AgentFSFuse { let file = self .runtime .block_on(async move { fs.open(ino as i64, flags).await })?; - agentfs_sdk::profiling::record_fuse_ino_file_resolution(); + crate::telemetry::record_fuse_ino_file_resolution(); let mut ino_files = self.ino_files.lock(); match ino_files.get_mut(&ino) { Some(entry) if write && !entry.write_capable => { entry.file = file.clone(); entry.write_capable = true; entry.last_used = stamp; - agentfs_sdk::profiling::record_fuse_ino_file_upgrade(); + crate::telemetry::record_fuse_ino_file_upgrade(); Ok(file) } Some(entry) => { @@ -2507,6 +2127,7 @@ impl AgentFSFuse { /// guarantees no further ops for `ino` without a fresh LOOKUP, so the /// entry is dropped after moving any buffered tail into the batcher. fn drop_ino_file(&self, ino: u64) { + self.caches.prune_external_read_state(ino); let drain = { let mut ino_files = self.ino_files.lock(); let Some(mut entry) = ino_files.remove(&ino) else { @@ -2520,7 +2141,10 @@ impl AgentFSFuse { }; if let Some(drain) = drain { if let Err(error) = flush_pending_batched_out_of_lock(&self.runtime, drain) { - tracing::warn!("FUSE::forget failed to flush pending writes for {ino}: {error}"); + tracing::warn!( + ?error, + "FUSE::forget failed to flush pending writes for {ino}" + ); } } } @@ -2559,58 +2183,73 @@ impl AgentFSFuse { return Ok(false); } self.flush_pending_inode(ino)?; - agentfs_sdk::profiling::record_fuse_pending_tail_drain(); + crate::telemetry::record_fuse_pending_tail_drain(); Ok(true) } fn keepcache_allows(&self, ino: u64, fingerprint: &KeepCacheFingerprint) -> bool { - self.keepcache_drift_guard.lock().allows(ino, fingerprint) + self.caches.keepcache_allows(ino, fingerprint) } fn mark_keepcache_eligible(&self, ino: u64, fingerprint: KeepCacheFingerprint) { - self.keepcache_drift_guard - .lock() - .mark_eligible(ino, fingerprint); + self.caches.mark_keepcache_eligible(ino, fingerprint); } fn drop_keepcache_eligibility(&self, ino: u64) { - if self.keepcache_drift_guard.lock().drop_eligibility(ino) { - agentfs_sdk::profiling::record_fuse_keepcache_eligibility_drop(); + if self.caches.drop_keepcache_eligibility(ino) { + crate::telemetry::record_fuse_keepcache_eligibility_drop(); } } - #[allow(dead_code)] + #[allow( + dead_code, + reason = "emergency pre-Tier-4 parity helper kept for one milestone" + )] fn drain_inode_writes(&self, ino: u64) -> Result<(), SdkError> { // Kept for emergency parity with pre-Tier-4 paths; not called on the // hot read path because the SDK overlay handles read-after-write // consistency without forcing a SQLite commit. - let fs = self.fs.clone(); self.runtime - .block_on(async move { fs.drain_inode_writes(ino as i64).await }) + .block_on(self.semantics.commit_barrier(Some(ino as i64))) + } + + fn commit_barrier(&self, ino: Option) -> Result<(), SdkError> { + self.runtime + .block_on(self.semantics.commit_barrier(ino.map(|ino| ino as i64))) } fn finalize_filesystem(&self) -> Result<(), SdkError> { - let fs = self.fs.clone(); - self.runtime.block_on(async move { fs.finalize().await }) + self.runtime.block_on(self.semantics.finalize()) } fn invalidate_inode_cache(&self, req: &Request, ino: u64) { - let _cache_reply = self.cache_reply_lock.lock(); - self.bump_cache_epoch(); - self.drop_keepcache_eligibility(ino); - self.invalidate_cached_inode(ino); - self.notify_inval_inode(req, ino, 0, i64::MAX); - agentfs_sdk::profiling::record_base_fast_inode_invalidation(); + let invalidation = self + .caches + .invalidate_inode(ino, NotifyPolicy::NotifyKernel); + if invalidation.dropped_keepcache { + crate::telemetry::record_fuse_keepcache_eligibility_drop(); + } + if invalidation.notify_kernel { + self.notify_inval_inode(req, ino, 0, i64::MAX); + crate::telemetry::record_base_fast_inode_invalidation(); + } record_mutation_invalidation(); } fn invalidate_entry_cache(&self, req: &Request, parent: u64, name: &OsStr) { - let _cache_reply = self.cache_reply_lock.lock(); - self.bump_cache_epoch(); + let mut notify_kernel = true; if let Some(name) = name.to_str() { - self.invalidate_cached_entry(parent, name); + let invalidation = + self.caches + .invalidate_entry(parent, name, NotifyPolicy::NotifyKernel); + if invalidation.removed_negative { + crate::telemetry::record_negative_cache_invalidation(); + } + notify_kernel = invalidation.notify_kernel; + } + if notify_kernel { + self.notify_inval_entry(req, parent, name); } - self.notify_inval_entry(req, parent, name); record_mutation_invalidation(); } @@ -2626,11 +2265,16 @@ impl AgentFSFuse { self.invalidate_inode_cache(req, ino); return; } - let _cache_reply = self.cache_reply_lock.lock(); - self.bump_cache_epoch(); - self.drop_keepcache_eligibility(ino); - self.invalidate_cached_inode(ino); - agentfs_sdk::profiling::record_base_fast_inode_invalidation(); + let invalidation = self + .caches + .invalidate_inode(ino, NotifyPolicy::SuppressKernel); + if invalidation.dropped_keepcache { + crate::telemetry::record_fuse_keepcache_eligibility_drop(); + } + if invalidation.notify_kernel { + self.notify_inval_inode(req, ino, 0, i64::MAX); + crate::telemetry::record_base_fast_inode_invalidation(); + } record_mutation_invalidation(); } @@ -2658,27 +2302,36 @@ impl AgentFSFuse { self.invalidate_entry_cache(req, parent, name); return; } - let _cache_reply = self.cache_reply_lock.lock(); - self.bump_cache_epoch(); if let Some(name) = name.to_str() { - self.invalidate_cached_entry(parent, name); + let invalidation = + self.caches + .invalidate_entry(parent, name, NotifyPolicy::SuppressKernel); + if invalidation.removed_negative { + crate::telemetry::record_negative_cache_invalidation(); + } + if invalidation.notify_kernel { + self.notify_inval_entry(req, parent, OsStr::new(name)); + } } record_mutation_invalidation(); } fn notify_inval_inode(&self, req: &Request, ino: u64, offset: i64, len: i64) { - agentfs_sdk::profiling::record_fuse_adapter_inval_inode_notification(); + crate::telemetry::record_fuse_adapter_inval_inode_notification(); if !self.sync_inval { req.deferred_notifier().inval_inode(ino, offset, len); return; } + self.notify_inval_inode_sync(req, ino, offset, len); + } + fn notify_inval_inode_sync(&self, req: &Request, ino: u64, offset: i64, len: i64) { let start = Instant::now(); let result = req.notifier().inval_inode(ino, offset, len); - agentfs_sdk::profiling::record_fuse_sync_inval_latency(start.elapsed()); + crate::telemetry::record_fuse_sync_inval_latency(start.elapsed()); match result { - Ok(()) => agentfs_sdk::profiling::record_fuse_sync_inval_inode_ok(), + Ok(()) => crate::telemetry::record_fuse_sync_inval_inode_ok(), Err(e) => { tracing::warn!( "synchronous FUSE inval_inode failed ino={}, offset={}, len={}: {}", @@ -2687,13 +2340,13 @@ impl AgentFSFuse { len, e ); - agentfs_sdk::profiling::record_fuse_sync_inval_inode_err(); + crate::telemetry::record_fuse_sync_inval_inode_err(); } } } fn notify_inval_entry(&self, req: &Request, parent: u64, name: &OsStr) { - agentfs_sdk::profiling::record_fuse_adapter_inval_entry_notification(); + crate::telemetry::record_fuse_adapter_inval_entry_notification(); if !self.sync_inval { req.deferred_notifier().inval_entry(parent, name); return; @@ -2701,10 +2354,10 @@ impl AgentFSFuse { let start = Instant::now(); let result = req.notifier().inval_entry(parent, name); - agentfs_sdk::profiling::record_fuse_sync_inval_latency(start.elapsed()); + crate::telemetry::record_fuse_sync_inval_latency(start.elapsed()); match result { - Ok(()) => agentfs_sdk::profiling::record_fuse_sync_inval_entry_ok(), + Ok(()) => crate::telemetry::record_fuse_sync_inval_entry_ok(), Err(e) => { tracing::warn!( "synchronous FUSE inval_entry failed parent={}, name={:?}: {}", @@ -2712,38 +2365,29 @@ impl AgentFSFuse { name, e ); - agentfs_sdk::profiling::record_fuse_sync_inval_entry_err(); + crate::telemetry::record_fuse_sync_inval_entry_err(); } } } - fn invalidate_cached_inode(&self, ino: u64) { - self.attr_cache.lock().remove(&ino); - self.entry_cache - .lock() - .retain(|_, stats| stats.ino as u64 != ino); - self.dir_entries_cache.lock().retain(|dir_ino, entries| { - *dir_ino != ino && !entries.iter().any(|entry| entry.attr.ino == ino) - }); - } - fn invalidate_cached_entry(&self, parent: u64, name: &str) { let key = (parent, name.to_string()); - self.entry_cache.lock().remove(&key); - if self.negative_entry_cache.lock().remove(&key).is_some() { - agentfs_sdk::profiling::record_negative_cache_invalidation(); + self.caches.entries().lock().remove(&key); + if self.caches.negative_entries().lock().remove(&key).is_some() { + crate::telemetry::record_negative_cache_invalidation(); } } fn cache_negative_entry(&self, parent: u64, name: &str) { let key = (parent, name.to_string()); - self.entry_cache.lock().remove(&key); - self.negative_entry_cache.lock().insert(key, ()); + self.caches.entries().lock().remove(&key); + self.caches.negative_entries().lock().insert(key, ()); } fn lookup_child_for_invalidation(&self, parent: u64, name: &str) -> Option { if let Some(stats) = self - .entry_cache + .caches + .entries() .lock() .get(&(parent, name.to_string())) .cloned() @@ -2759,24 +2403,37 @@ impl AgentFSFuse { } fn cache_attr(&self, stats: &Stats) { - self.attr_cache + if !self.attrs_cacheable() || self.external_drift_sensitive(stats.ino as u64) { + return; + } + self.caches + .attrs() .lock() .insert(stats.ino as u64, stats.clone()); } fn cache_entry(&self, parent: u64, name: &str, stats: &Stats) { + if !self.attrs_cacheable() || self.external_drift_sensitive(stats.ino as u64) { + return; + } self.cache_attr(stats); self.invalidate_cached_entry(parent, name); - self.entry_cache + self.caches + .entries() .lock() .insert((parent, name.to_string()), stats.clone()); } fn cached_attr(&self, ino: u64) -> Result, SdkError> { let cache_epoch = self.cache_epoch(); - if let Some(stats) = self.attr_cache.lock().get(&ino).cloned() { - let cache_reply = self.cache_reply_lock.try_lock(); - if cache_reply.is_some() && !self.cache_epoch_changed(cache_epoch) { + let cached_attr = if self.attrs_cacheable() { + self.caches.attrs().lock().get(&ino).cloned() + } else { + None + }; + if let Some(stats) = cached_attr { + let cache_reply = self.caches.try_reply_guard(cache_epoch); + if cache_reply.is_some() { return Ok(Some(stats)); } } @@ -2787,9 +2444,9 @@ impl AgentFSFuse { .runtime .block_on(async move { fs.getattr(ino as i64).await })?; - let cache_reply = self.cache_reply_lock.try_lock(); + let cache_reply = self.caches.try_reply_guard(cache_epoch); if let Some(ref stats) = stats { - if cache_reply.is_some() && !self.cache_epoch_changed(cache_epoch) { + if cache_reply.is_some() && self.attrs_cacheable() { self.cache_attr(stats); } } @@ -2801,10 +2458,16 @@ impl AgentFSFuse { &self, ino: u64, ) -> Result<(Arc>, bool, u64), SdkError> { + let dir_cacheable = self.attrs_cacheable() && !self.external_drift_sensitive(ino); let cache_epoch = self.cache_epoch(); - if let Some(entries) = self.dir_entries_cache.lock().get(&ino).cloned() { - let cache_reply = self.cache_reply_lock.try_lock(); - if cache_reply.is_some() && !self.cache_epoch_changed(cache_epoch) { + let cached_entries = if dir_cacheable { + self.caches.dir_entries().lock().get(&ino).cloned() + } else { + None + }; + if let Some(entries) = cached_entries { + let cache_reply = self.caches.try_reply_guard(cache_epoch); + if cache_reply.is_some() { return Ok((entries, true, cache_epoch)); } } @@ -2857,7 +2520,7 @@ impl AgentFSFuse { } else { for tail_ino in affected { self.flush_pending_inode(tail_ino)?; - agentfs_sdk::profiling::record_fuse_pending_tail_drain(); + crate::telemetry::record_fuse_pending_tail_drain(); } let fs = self.fs.clone(); match self @@ -2886,10 +2549,10 @@ impl AgentFSFuse { self.cached_attr(1)? .ok_or_else(|| SdkError::from(FsError::NotFound))? }; - let cache_reply = self.cache_reply_lock.try_lock(); - stable = stable && cache_reply.is_some() && !self.cache_epoch_changed(stable_epoch); + let cache_reply = self.caches.try_reply_guard(stable_epoch); + stable = stable && cache_reply.is_some(); - if stable { + if stable && dir_cacheable { for entry in &entries { self.cache_entry(ino, &entry.name, &entry.stats); } @@ -2897,8 +2560,11 @@ impl AgentFSFuse { let all_entries = build_cached_readdir_entries(&dir_stats, &parent_stats, entries); let entries = Arc::new(all_entries); - if stable { - self.dir_entries_cache.lock().insert(ino, entries.clone()); + if stable && dir_cacheable { + self.caches + .dir_entries() + .lock() + .insert(ino, entries.clone()); } Ok((entries, stable, stable_epoch)) } @@ -2907,66 +2573,36 @@ impl AgentFSFuse { /// /// The provided Tokio runtime is used to execute async FileSystem operations /// from within synchronous FUSE callbacks via `block_on`. - fn new(fs: Arc, runtime: Runtime) -> Self { - let sync_inval = fuse_sync_inval_enabled_from_env(); - let self_inval = env_flag_default("AGENTFS_FUSE_SELF_INVAL", false); - let drain_on_release = fuse_drain_on_release_from_env(); - let drain_on_forget = fuse_drain_on_forget_from_env(); - let flush_inval_always = env_flag_default("AGENTFS_FUSE_FLUSH_INVAL", false); - let noflush = env_flag_default("AGENTFS_FUSE_NOFLUSH", true) && !drain_on_release; - if noflush != env_flag_default("AGENTFS_FUSE_NOFLUSH", true) { - tracing::warn!( - "AGENTFS_FUSE_NOFLUSH disabled: AGENTFS_DRAIN_ON_RELEASE needs the close-time FLUSH" - ); - } - let noopen = env_flag_default("AGENTFS_FUSE_NOOPEN", true) && !drain_on_release; - if noopen != env_flag_default("AGENTFS_FUSE_NOOPEN", true) { - tracing::warn!( - "AGENTFS_FUSE_NOOPEN disabled: AGENTFS_DRAIN_ON_RELEASE needs per-handle releases" - ); - } - let ino_files_cap = std::env::var("AGENTFS_FUSE_INO_FILES_CAP") - .ok() - .and_then(|v| v.parse::().ok()) - .filter(|cap| *cap >= 16) - .unwrap_or(65_536); - let cache_config = FuseKernelCacheConfig::from_env(); + fn new(fs: Arc, runtime: Runtime, config: FuseConfig) -> Self { + let keepcache_delta_enabled = fs.delta_keep_cache_fast_path(); + let cache_config = config.kernel_cache(); cache_config.record_profile(); - let cache_dir_enabled = - env_flag_default("AGENTFS_FUSE_CACHE_DIR", true) && cache_config.keepcache_enabled; + let cache_dir_enabled = config.cache_dir_enabled(); let writeback_enabled = cache_config.writeback_cache_enabled; + let uring = config.uring; Self { + semantics: Semantics::new(fs.clone()), fs, runtime, cache_config, + uring, open_files: Arc::new(Mutex::new(HashMap::new())), - dir_entries_cache: Arc::new(Mutex::new(HashMap::new())), - attr_cache: Arc::new(Mutex::new(HashMap::new())), - entry_cache: Arc::new(Mutex::new(HashMap::new())), - negative_entry_cache: Arc::new(Mutex::new(HashMap::new())), - keepcache_drift_guard: Arc::new(Mutex::new(KeepCacheDriftGuard::new( - env_flag_default("AGENTFS_FUSE_STICKY_KEEPCACHE_DROP", false), - ))), - cache_reply_lock: Arc::new(Mutex::new(())), - cache_epoch: AtomicU64::new(0), + caches: AdapterCaches::new(config.keepcache_sticky_drop), next_fh: AtomicU64::new(1), - sync_inval, - self_inval, - drain_on_release, - drain_on_forget, - flush_inval_always, - noflush, - keepcache_delta_enabled: agentfs_sdk::filesystem::keepcache_delta_enabled(), - noopen, + sync_inval: config.sync_inval, + self_inval: config.self_inval, + drain_on_release: config.drain_on_release, + drain_on_forget: config.drain_on_forget, + flush_inval_always: config.flush_inval_always, + noflush: config.noflush, + keepcache_delta_enabled, + noopen: config.noopen, noopen_active: AtomicBool::new(false), ino_files: Mutex::new(HashMap::new()), - ino_files_cap, + ino_files_cap: config.ino_files_cap, ino_file_stamp: AtomicU64::new(0), pending_dirty_handles: AtomicUsize::new(0), cache_dir_enabled, - _profile_report: Arc::new(agentfs_sdk::profiling::ProfileReportGuard::new( - "fuse_session", - )), writeback_enabled, } } @@ -2988,270 +2624,48 @@ fn fuse_write_open(flags: i32) -> bool { (flags & libc::O_ACCMODE) != libc::O_RDONLY || (flags & libc::O_TRUNC) != 0 } -fn env_bool(value: &str) -> Option { - match value.trim() { - "1" => Some(true), - "0" => Some(false), - value - if value.eq_ignore_ascii_case("true") - || value.eq_ignore_ascii_case("yes") - || value.eq_ignore_ascii_case("on") => - { - Some(true) - } - value - if value.eq_ignore_ascii_case("false") - || value.eq_ignore_ascii_case("no") - || value.eq_ignore_ascii_case("off") => - { - Some(false) - } - _ => None, - } -} - -fn env_duration_ms(name: &str, default: u64) -> u64 { - match std::env::var(name) { - Ok(value) => match value.parse::() { - Ok(ms) => ms, - Err(_) => { - tracing::warn!( - "Ignoring invalid {}={} for FUSE TTL; using {}ms", - name, - value, - default - ); - default - } - }, - Err(_) => default, - } -} - -fn env_flag_default(name: &str, default: bool) -> bool { - match std::env::var(name) { - Ok(value) => env_bool(&value).unwrap_or_else(|| { - tracing::warn!( - "Ignoring invalid {}={} for FUSE kernel cache flag; using default {}", - name, - value, - default - ); - default - }), - Err(_) => default, - } -} - -fn fuse_workers_serial_from_env() -> bool { - std::env::var("AGENTFS_FUSE_WORKERS") - .map(|value| { - let value = value.trim(); - value.eq_ignore_ascii_case("serial") || value == "0" - }) - // Default (unset): parallel dispatch so kernel cache invariants hold. - // Pair with the matching default in cli/src/fuser/session.rs::FuseDispatchMode::from_env. - .unwrap_or(false) -} - -fn fuse_workers_not_serial_from_env() -> bool { - !fuse_workers_serial_from_env() -} - -/// Whether flush/release should force a synchronous SDK drain (SQLite commit). -/// -/// Default false: the Tier-4 overlay serves reads from pending writes, so a -/// commit on every close is unnecessary and serialises the clone critical path. -/// Durability is preserved by fsync, the batcher timer/bytes/global triggers, -/// and finalize-on-unmount. `AGENTFS_DRAIN_ON_RELEASE=1` restores the legacy -/// commit-on-close behaviour (a kill switch for the deferral). -fn fuse_drain_on_release_from_env() -> bool { - match std::env::var("AGENTFS_DRAIN_ON_RELEASE") { - Ok(value) => match env_bool(&value) { - Some(enabled) => enabled, - None => { - tracing::warn!( - "Ignoring invalid AGENTFS_DRAIN_ON_RELEASE={:?}; expected 0/1/true/false", - value - ); - false - } - }, - Err(_) => false, - } -} - -/// Whether FUSE forget/batch_forget should force a synchronous SDK drain -/// (SQLite commit) for the forgotten inode. -/// -/// Default false: a kernel FORGET only drops the kernel's reference to the -/// inode — the SDK's pending batched writes stay readable through the Tier-4 -/// overlay and are committed by the batcher timer/bytes triggers, fsync, or -/// finalize-on-unmount. Draining here used to issue one serial SQLite commit -/// per written file during git-clone-style workloads (the kernel FORGETs each -/// file shortly after our post-write entry invalidation), which sat on the -/// clone critical path. `AGENTFS_DRAIN_ON_FORGET=1` restores the legacy -/// commit-on-forget behaviour (a kill switch for the deferral). -fn fuse_drain_on_forget_from_env() -> bool { - match std::env::var("AGENTFS_DRAIN_ON_FORGET") { - Ok(value) => match env_bool(&value) { - Some(enabled) => enabled, - None => { - tracing::warn!( - "Ignoring invalid AGENTFS_DRAIN_ON_FORGET={:?}; expected 0/1/true/false", - value - ); - false - } - }, - Err(_) => false, - } -} - -fn fuse_sync_inval_enabled_from_env() -> bool { - let workers_serial = fuse_workers_serial_from_env(); - let sync_requested = match std::env::var("AGENTFS_FUSE_SYNC_INVAL") { - Ok(value) => match env_bool(&value) { - Some(enabled) => enabled, - None => { - tracing::warn!( - "Ignoring invalid AGENTFS_FUSE_SYNC_INVAL={:?}; expected 0/1/true/false", - value - ); - // Fall back to deferred invalidation (the safe default). - false - } - }, - // Default (unset): use deferred invalidation. Synchronous writev of - // FUSE_NOTIFY_INVAL_* from a request handler can deadlock with the - // kernel: the notify triggers d_invalidate -> iput -> FUSE_FORGET, and - // the kernel may block the writev call until that FORGET is delivered. - // In parallel mode this surfaces under git workloads (clone, checkout) - // when the session thread is blocked on a full worker queue and cannot - // read the pending FORGET. The DeferredNotifier thread is the only - // path that's safe in both serial and parallel modes, so it is the - // default. Users who explicitly opt into AGENTFS_FUSE_SYNC_INVAL=1 - // accept the deadlock risk in exchange for tighter cache coherency. - Err(_) => false, - }; - - if workers_serial && sync_requested { - tracing::info!( - "AGENTFS_FUSE_SYNC_INVAL requested with AGENTFS_FUSE_WORKERS=serial; using deferred invalidation to avoid notify/reply deadlock" - ); - false - } else { - sync_requested - } -} - -fn readdirplus_mode_from_env() -> ReaddirPlusMode { - match std::env::var("AGENTFS_FUSE_READDIRPLUS") { - Ok(value) - if value.eq_ignore_ascii_case("off") - || value.eq_ignore_ascii_case("false") - || value.eq_ignore_ascii_case("no") - || value == "0" => - { - ReaddirPlusMode::Off - } - Ok(value) if value.eq_ignore_ascii_case("auto") => ReaddirPlusMode::Auto, - Ok(value) - if value.eq_ignore_ascii_case("always") - || value.eq_ignore_ascii_case("on") - || value.eq_ignore_ascii_case("true") - || value.eq_ignore_ascii_case("yes") - || value == "1" => - { - ReaddirPlusMode::Always - } - Ok(value) => { - tracing::warn!( - "Ignoring invalid AGENTFS_FUSE_READDIRPLUS={}; disabling readdirplus", - value - ); - ReaddirPlusMode::Off - } - // Default ON: profiling shows `always` strictly reduces metadata - // round-trips (diff -34%, status -6.6%, checkout getattr -10.5%) with no - // regressions and identical invalidation safety. `auto`/`off` remain - // available as explicit rollbacks. - Err(_) => ReaddirPlusMode::Always, - } -} - -impl ReaddirPlusMode { - fn profile_value(self) -> u64 { - match self { - ReaddirPlusMode::Off => READDIRPLUS_MODE_OFF, - ReaddirPlusMode::Auto => READDIRPLUS_MODE_AUTO, - ReaddirPlusMode::Always => READDIRPLUS_MODE_ALWAYS, - } - } -} - fn configure_writeback_cache(config: &mut KernelConfig, enabled: bool) { if !enabled { - agentfs_sdk::profiling::set_fuse_writeback_cache_enabled(false); + crate::telemetry::set_fuse_writeback_cache_enabled(false); return; } match config.add_capabilities(FUSE_WRITEBACK_CACHE) { - Ok(()) => agentfs_sdk::profiling::set_fuse_writeback_cache_enabled(true), + Ok(()) => crate::telemetry::set_fuse_writeback_cache_enabled(true), Err(_) => { tracing::warn!("Kernel does not support FUSE_WRITEBACK_CACHE; leaving it disabled"); - agentfs_sdk::profiling::set_fuse_writeback_cache_enabled(false); + crate::telemetry::set_fuse_writeback_cache_enabled(false); } } } fn configure_readdirplus(config: &mut KernelConfig, mode: ReaddirPlusMode) { - agentfs_sdk::profiling::set_fuse_readdirplus_mode(mode.profile_value()); - - // FUSE_READDIRPLUS opcode 44 is decoded by the vendored fuser dispatcher - // only when the `abi-7-21` feature is enabled. If we advertised the - // capability without that feature, the kernel would send opcode 44 and the - // dispatcher would return ENOSYS, breaking readdir on the mount. Gating the - // capability negotiation here turns the mismatch into a compile-time - // expectation rather than a runtime kernel error. - #[cfg(not(feature = "abi-7-21"))] - { - if !matches!(mode, ReaddirPlusMode::Off) { - tracing::warn!( - ?mode, - "AGENTFS_FUSE_READDIRPLUS requested but cli compiled without abi-7-21 feature; \ - capability not advertised (kernel would send opcodes the dispatcher cannot decode)" - ); - } - let _ = config; - return; - } + crate::telemetry::set_fuse_readdirplus_mode(mode.profile_value()); - #[cfg(feature = "abi-7-21")] match mode { ReaddirPlusMode::Off => {} ReaddirPlusMode::Auto => { - agentfs_sdk::profiling::record_fuse_readdirplus_auto_requested(); + crate::telemetry::record_fuse_readdirplus_auto_requested(); match config.add_capabilities(FUSE_DO_READDIRPLUS) { - Ok(()) => agentfs_sdk::profiling::record_fuse_readdirplus_do_enabled(), + Ok(()) => crate::telemetry::record_fuse_readdirplus_do_enabled(), Err(_) => { tracing::warn!("Kernel does not support FUSE_DO_READDIRPLUS"); - agentfs_sdk::profiling::record_fuse_readdirplus_unsupported(); + crate::telemetry::record_fuse_readdirplus_unsupported(); } } match config.add_capabilities(FUSE_READDIRPLUS_AUTO) { - Ok(()) => agentfs_sdk::profiling::record_fuse_readdirplus_auto_enabled(), + Ok(()) => crate::telemetry::record_fuse_readdirplus_auto_enabled(), Err(_) => { tracing::warn!("Kernel does not support FUSE_READDIRPLUS_AUTO"); - agentfs_sdk::profiling::record_fuse_readdirplus_unsupported(); + crate::telemetry::record_fuse_readdirplus_unsupported(); } } } ReaddirPlusMode::Always => { - agentfs_sdk::profiling::record_fuse_readdirplus_do_requested(); + crate::telemetry::record_fuse_readdirplus_do_requested(); match config.add_capabilities(FUSE_DO_READDIRPLUS) { - Ok(()) => agentfs_sdk::profiling::record_fuse_readdirplus_do_enabled(), - Err(_) => agentfs_sdk::profiling::record_fuse_readdirplus_unsupported(), + Ok(()) => crate::telemetry::record_fuse_readdirplus_do_enabled(), + Err(_) => crate::telemetry::record_fuse_readdirplus_unsupported(), } } } @@ -3356,19 +2770,69 @@ fn allow_other_supported() -> bool { false } +pub struct SessionHandle { + thread: Option>>, + unmounter: crate::transport::SessionUnmounter, +} + +impl SessionHandle { + pub fn unmount(&mut self) -> std::io::Result<()> { + self.unmounter.unmount() + } + + pub fn is_finished(&self) -> bool { + self.thread + .as_ref() + .map(std::thread::JoinHandle::is_finished) + .unwrap_or(true) + } + + pub fn join(mut self) -> anyhow::Result<()> { + let Some(thread) = self.thread.take() else { + return Ok(()); + }; + match thread.join() { + Ok(result) => result, + Err(panic) => Err(anyhow::anyhow!("FUSE session thread panicked: {panic:?}")), + } + } +} + pub fn mount( fs: Arc, opts: FuseMountOptions, runtime: Runtime, -) -> anyhow::Result<()> { +) -> anyhow::Result { + crate::telemetry::register_sections(); // Raise fd limit to hard limit to prevent "too many open files" errors // when passthrough filesystems cache O_PATH file descriptors maximize_fd_limit(); - let fs = AgentFSFuse::new(fs, runtime); + let fuse_config = FuseConfig::from_env(); + let dispatch_mode = fuse_config.dispatch_mode; + let uring_config = fuse_config.uring; + let fs = AgentFSFuse::new(fs, runtime, fuse_config); + let mount_opts = build_mount_options(&opts)?; + + let mut session = crate::transport::Session::new( + fs, + &opts.mountpoint, + &mount_opts, + dispatch_mode, + uring_config, + )?; + let unmounter = session.unmount_callable(); + let thread = std::thread::spawn(move || session.run().map_err(anyhow::Error::from)); + + Ok(SessionHandle { + thread: Some(thread), + unmounter, + }) +} +fn build_mount_options(opts: &FuseMountOptions) -> anyhow::Result> { let mut mount_opts = vec![ - MountOption::FSName(opts.fsname), + MountOption::FSName(opts.fsname.clone()), // Enable kernel-level permission checking based on file mode/uid/gid MountOption::DefaultPermissions, ]; @@ -3393,68 +2857,20 @@ pub fn mount( mount_opts.push(MountOption::AllowRoot); } - crate::fuser::mount2(fs, &opts.mountpoint, &mount_opts)?; - - Ok(()) + crate::transport::check_option_conflicts(&mount_opts)?; + Ok(mount_opts) } #[cfg(test)] mod tests { use super::{ - build_cached_readdir_entries, fuse_write_open, readdir_start, OpenFile, WriteBuffer, + build_cached_readdir_entries, build_mount_options, fuse_write_open, readdir_start, + FuseMountOptions, }; - use agentfs_sdk::filesystem::{DirEntry, Stats, WriteRange, S_IFDIR, S_IFLNK, S_IFREG}; - use agentfs_sdk::{BoxedFile, File}; - use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, Mutex, - }; - use tokio::runtime::Runtime; - - fn ranges(buffer: &WriteBuffer) -> Vec<(u64, Vec)> { - buffer - .ranges_for_flush() - .into_iter() - .map(|range| (range.offset, range.data)) - .collect() - } - - #[derive(Default)] - struct RecordingFile { - pwrite_calls: AtomicUsize, - pwrite_ranges_calls: AtomicUsize, - ranges: Mutex>, - } - - #[async_trait::async_trait] - impl File for RecordingFile { - async fn pread(&self, _offset: u64, _size: u64) -> agentfs_sdk::error::Result> { - Ok(Vec::new()) - } - - async fn pwrite(&self, _offset: u64, _data: &[u8]) -> agentfs_sdk::error::Result<()> { - self.pwrite_calls.fetch_add(1, Ordering::SeqCst); - Ok(()) - } - - async fn pwrite_ranges(&self, ranges: Vec) -> agentfs_sdk::error::Result<()> { - self.pwrite_ranges_calls.fetch_add(1, Ordering::SeqCst); - *self.ranges.lock().unwrap() = ranges; - Ok(()) - } - - async fn truncate(&self, _size: u64) -> agentfs_sdk::error::Result<()> { - Ok(()) - } - - async fn fsync(&self) -> agentfs_sdk::error::Result<()> { - Ok(()) - } - - async fn fstat(&self) -> agentfs_sdk::error::Result { - Ok(stats(1, S_IFREG | 0o644)) - } - } + use agentfs_core::fs::{DirEntry, Stats, S_IFDIR, S_IFLNK, S_IFREG}; + use agentfs_core::semantics::access; + use smallvec::smallvec; + use std::path::PathBuf; fn stats(ino: i64, mode: u32) -> Stats { Stats { @@ -3474,39 +2890,135 @@ mod tests { } } - #[test] - fn readdir_start_clamps_negative_offsets_to_beginning() { - assert_eq!(readdir_start(-1), 0); - assert_eq!(readdir_start(0), 0); - assert_eq!(readdir_start(2), 2); + fn stats_with_owner(ino: i64, mode: u32, uid: u32, gid: u32) -> Stats { + Stats { + uid, + gid, + ..stats(ino, mode) + } } #[test] - fn readdirplus_mode_defaults_to_always_with_rollbacks() { - use super::{readdirplus_mode_from_env, ReaddirPlusMode}; - - let key = "AGENTFS_FUSE_READDIRPLUS"; - let saved = std::env::var(key).ok(); - - std::env::remove_var(key); - assert_eq!(readdirplus_mode_from_env(), ReaddirPlusMode::Always); - - std::env::set_var(key, "auto"); - assert_eq!(readdirplus_mode_from_env(), ReaddirPlusMode::Auto); - - std::env::set_var(key, "off"); - assert_eq!(readdirplus_mode_from_env(), ReaddirPlusMode::Off); + fn fuse_mount_keeps_kernel_default_permissions_enabled() { + let opts = FuseMountOptions { + mountpoint: PathBuf::from("/tmp/agentfs-test"), + auto_unmount: false, + allow_root: false, + allow_other: false, + fsname: "agentfs-test".to_string(), + uid: None, + gid: None, + }; - std::env::set_var(key, "always"); - assert_eq!(readdirplus_mode_from_env(), ReaddirPlusMode::Always); + let options = build_mount_options(&opts).expect("mount options build"); - std::env::set_var(key, "garbage"); - assert_eq!(readdirplus_mode_from_env(), ReaddirPlusMode::Off); + assert!( + options.contains(&crate::transport::MountOption::DefaultPermissions), + "FUSE adapter must keep kernel default_permissions as the access authority" + ); + } - match saved { - Some(value) => std::env::set_var(key, value), - None => std::env::remove_var(key), + #[test] + fn fuse_default_permissions_cases_match_shared_access() { + let owner = access::Credentials { + uid: 1000, + gid: 1000, + groups: smallvec![], + }; + let other = access::Credentials { + uid: 3000, + gid: 3000, + groups: smallvec![], + }; + let root = access::Credentials { + uid: 0, + gid: 0, + groups: smallvec![], + }; + let cases = [ + ( + "fuse owner file rw", + stats_with_owner(20, S_IFREG | 0o600, 1000, 1000), + owner.clone(), + true, + true, + false, + ), + ( + "fuse other denied", + stats_with_owner(21, S_IFREG | 0o600, 1000, 1000), + other.clone(), + false, + false, + false, + ), + ( + "fuse auxiliary group execute directory", + stats_with_owner(22, S_IFDIR | 0o010, 1000, 2000), + access::Credentials { + uid: 3000, + gid: 3000, + groups: smallvec![2000], + }, + false, + false, + true, + ), + ( + "fuse primary group write without search directory", + stats_with_owner(23, S_IFDIR | 0o020, 1000, 2000), + access::Credentials { + uid: 3000, + gid: 2000, + groups: smallvec![], + }, + false, + true, + false, + ), + ( + "fuse other execute-only file", + stats_with_owner(24, S_IFREG | 0o001, 1000, 1000), + other, + false, + false, + true, + ), + ( + "fuse root directory search", + stats_with_owner(25, S_IFDIR, 1000, 1000), + root, + true, + true, + true, + ), + ]; + + println!("adapter=fuse authority=kernel_default_permissions surface=semantics::access"); + let mut mismatches = 0usize; + for (name, stats, creds, read, write, search) in cases { + let got = ( + access::may_read(&stats, &creds), + access::may_write(&stats, &creds), + access::may_search(&stats, &creds), + ); + println!( + "{name}: fuse=({read},{write},{search}) access=({},{},{})", + got.0, got.1, got.2 + ); + if got != (read, write, search) { + mismatches += 1; + } } + println!("adapter=fuse access_conformance mismatches={mismatches}"); + assert_eq!(mismatches, 0); + } + + #[test] + fn readdir_start_clamps_negative_offsets_to_beginning() { + assert_eq!(readdir_start(-1), 0); + assert_eq!(readdir_start(0), 0); + assert_eq!(readdir_start(2), 2); } #[test] @@ -3531,10 +3043,12 @@ mod tests { DirEntry { name: "file.txt".to_string(), stats: child, + cookie: 1, }, DirEntry { name: "link".to_string(), stats: symlink, + cookie: 2, }, ], ); @@ -3542,111 +3056,18 @@ mod tests { assert_eq!(entries.len(), 4); assert_eq!(entries[0].name, "."); assert_eq!(entries[0].attr.ino, 10); - assert_eq!(entries[0].attr.kind, crate::fuser::FileType::Directory); + assert_eq!(entries[0].attr.kind, crate::transport::FileType::Directory); assert_eq!(entries[1].name, ".."); assert_eq!(entries[1].attr.ino, 1); - assert_eq!(entries[1].attr.kind, crate::fuser::FileType::Directory); + assert_eq!(entries[1].attr.kind, crate::transport::FileType::Directory); assert_eq!(entries[2].name, "file.txt"); assert_eq!(entries[2].attr.ino, 11); - assert_eq!(entries[2].attr.kind, crate::fuser::FileType::RegularFile); - assert_eq!(entries[3].name, "link"); - assert_eq!(entries[3].attr.ino, 12); - assert_eq!(entries[3].attr.kind, crate::fuser::FileType::Symlink); - } - - #[test] - fn write_buffer_merges_adjacent_ranges() { - let mut buffer = WriteBuffer::default(); - - buffer.write(0, b"hello").unwrap(); - buffer.write(5, b" world").unwrap(); - - assert_eq!(buffer.bytes(), 11); - assert_eq!(ranges(&buffer), vec![(0, b"hello world".to_vec())]); - } - - #[test] - fn write_buffer_overlays_overlapping_writes() { - let mut buffer = WriteBuffer::default(); - - buffer.write(0, b"abcdef").unwrap(); - buffer.write(2, b"ZZ").unwrap(); - - assert_eq!(buffer.bytes(), 6); - assert_eq!(ranges(&buffer), vec![(0, b"abZZef".to_vec())]); - } - - #[test] - fn write_buffer_overlays_following_range() { - let mut buffer = WriteBuffer::default(); - - buffer.write(10, b"abc").unwrap(); - buffer.write(8, b"ZZZZ").unwrap(); - - assert_eq!(buffer.bytes(), 5); - assert_eq!(ranges(&buffer), vec![(8, b"ZZZZc".to_vec())]); - } - - #[test] - fn write_buffer_bridges_two_existing_ranges() { - let mut buffer = WriteBuffer::default(); - - buffer.write(0, b"ab").unwrap(); - buffer.write(4, b"ef").unwrap(); - buffer.write(2, b"cd").unwrap(); - - assert_eq!(buffer.bytes(), 6); - assert_eq!(ranges(&buffer), vec![(0, b"abcdef".to_vec())]); - } - - #[test] - fn write_buffer_keeps_disjoint_ranges_ordered() { - let mut buffer = WriteBuffer::default(); - - buffer.write(10, b"tail").unwrap(); - buffer.write(0, b"head").unwrap(); - - assert_eq!(buffer.bytes(), 8); - assert_eq!( - ranges(&buffer), - vec![(0, b"head".to_vec()), (10, b"tail".to_vec())] - ); - } - - #[test] - fn write_buffer_rejects_offset_overflow() { - let mut buffer = WriteBuffer::default(); - - assert_eq!(buffer.write(u64::MAX, b"x"), Err(libc::EINVAL)); - assert!(buffer.is_empty()); - } - - #[test] - fn open_file_flushes_pending_writes_with_batch_api() { - let runtime = Runtime::new().unwrap(); - let recorder = Arc::new(RecordingFile::default()); - let file: BoxedFile = recorder.clone(); - let mut open_file = OpenFile::new(1, file); - - open_file.buffer_write(0, b"head").unwrap(); - open_file.buffer_write(10, b"tail").unwrap(); - open_file.flush_pending(&runtime).unwrap(); - - assert_eq!(recorder.pwrite_calls.load(Ordering::SeqCst), 0); - assert_eq!(recorder.pwrite_ranges_calls.load(Ordering::SeqCst), 1); assert_eq!( - *recorder.ranges.lock().unwrap(), - vec![ - WriteRange { - offset: 0, - data: b"head".to_vec(), - }, - WriteRange { - offset: 10, - data: b"tail".to_vec(), - }, - ] + entries[2].attr.kind, + crate::transport::FileType::RegularFile ); - assert!(open_file.pending.is_empty()); + assert_eq!(entries[3].name, "link"); + assert_eq!(entries[3].attr.ino, 12); + assert_eq!(entries[3].attr.kind, crate::transport::FileType::Symlink); } } diff --git a/crates/agentfs-fuse/src/adapter/write_buffer.rs b/crates/agentfs-fuse/src/adapter/write_buffer.rs new file mode 100644 index 00000000..4d10e963 --- /dev/null +++ b/crates/agentfs-fuse/src/adapter/write_buffer.rs @@ -0,0 +1,377 @@ +use agentfs_core::fs::WriteRange; +use agentfs_core::{error::Error as SdkError, BoxedFile}; +use std::collections::BTreeMap; +use tokio::runtime::Runtime; + +/// Threshold at which the FUSE-layer per-fh write coalescer flushes its +/// accumulated ranges down to the SDK. Picked at 4x the chunk size so a single +/// flushed call covers a few SQLite chunks and the AsyncMutex acquisition in +/// the SDK write batcher is amortised across many FUSE_WRITE requests for the +/// same handle. Smaller writes (the common git-clone case) accumulate in this +/// buffer until `flush` / `release` arrives and only then hit the SDK. +pub(super) const FUSE_COALESCE_FLUSH_BYTES: usize = 256 * 1024; + +/// Tracks an open FUSE file handle and its pending coalesced writes. +pub(super) struct OpenFile { + /// Inode associated with this FUSE file handle. + pub(super) ino: u64, + /// The file handle from the filesystem layer. + pub(super) file: BoxedFile, + /// Pending writes buffered for coalescing before reaching the filesystem layer. + pub(super) pending: WriteBuffer, +} + +impl OpenFile { + pub(super) fn new(ino: u64, file: BoxedFile) -> Self { + Self { + ino, + file, + pending: WriteBuffer::default(), + } + } + + #[cfg(test)] + pub(super) fn buffer_write(&mut self, offset: u64, data: &[u8]) -> Result<(), i32> { + self.pending.write(offset, data)?; + Ok(()) + } + + /// Coalesce a single FUSE write into the per-fh pending buffer. Returns + /// `true` if the cumulative buffer size has reached the flush threshold + /// and the caller should drain it before replying to the kernel. + pub(super) fn buffer_fuse_write(&mut self, offset: u64, data: &[u8]) -> Result { + self.pending.write(offset, data)?; + Ok(self.pending.bytes >= FUSE_COALESCE_FLUSH_BYTES) + } + + /// Drain the per-fh pending buffer into a `(file, ranges, range_count, + /// byte_count)` tuple so the caller can release the surrounding + /// `open_files` lock before issuing the async `pwrite_ranges*` call. The + /// hot write path MUST NOT hold the parking_lot `open_files` mutex across + /// `runtime.block_on(...)`: doing so serializes every other FUSE handler + /// behind one fh's SQLite commit and was the source of a 2x checkout + /// regression observed in the first Tier Two benchmark pass. + pub(super) fn take_pending(&mut self) -> Option { + if self.pending.is_empty() { + return None; + } + let file = self.file.clone(); + let ranges = self.pending.ranges_for_flush(); + let range_count = ranges.len() as u64; + let byte_count = ranges + .iter() + .map(|range| range.data.len() as u64) + .sum::(); + self.pending.clear(); + Some((file, ranges, range_count, byte_count)) + } + + /// Synchronous flush via the non-batched pwrite API. Production code uses + /// `take_pending` + `flush_pending_batched_out_of_lock` instead; this + /// remains as a test-only convenience so the OpenFile unit tests stay + /// readable. + #[cfg(test)] + pub(super) fn flush_pending(&mut self, runtime: &Runtime) -> Result<(), SdkError> { + let Some((file, ranges, range_count, byte_count)) = self.take_pending() else { + return Ok(()); + }; + + runtime.block_on(async move { file.pwrite_ranges(ranges).await })?; + crate::telemetry::record_fuse_flush(range_count, byte_count); + Ok(()) + } +} + +/// `(file, ranges, range_count, byte_count)` drained from a pending +/// `WriteBuffer`; flushed out-of-lock via `flush_pending_batched_out_of_lock`. +pub(super) type PendingDrain = (BoxedFile, Vec, u64, u64); + +/// Flush a `(file, ranges, range_count, byte_count)` tuple produced by +/// `OpenFile::take_pending()` via the SDK write batcher (so the coalesced +/// ranges enter the cross-inode batched-commit path). Called by the FUSE +/// write / flush / release handlers AFTER they have released the +/// `open_files` parking_lot mutex. +pub(super) fn flush_pending_batched_out_of_lock( + runtime: &Runtime, + drain: PendingDrain, +) -> Result<(), SdkError> { + let (file, ranges, range_count, byte_count) = drain; + runtime.block_on(async move { file.pwrite_ranges_batched(ranges).await })?; + crate::telemetry::record_fuse_flush(range_count, byte_count); + Ok(()) +} + +/// Pending write ranges for one open FUSE file handle. +/// +/// Ranges are keyed by start offset and kept non-overlapping. Adjacent and +/// overlapping writes are merged eagerly so common sequential writes become one +/// filesystem-layer `pwrite` when the handle is flushed. +#[derive(Default)] +pub(super) struct WriteBuffer { + pub(super) ranges: BTreeMap>, + pub(super) bytes: usize, +} + +impl WriteBuffer { + pub(super) fn is_empty(&self) -> bool { + self.ranges.is_empty() + } + + #[cfg(test)] + pub(super) fn bytes(&self) -> usize { + self.bytes + } + + pub(super) fn clear(&mut self) { + self.ranges.clear(); + self.bytes = 0; + } + + pub(super) fn ranges_for_flush(&self) -> Vec { + self.ranges + .iter() + .map(|(&offset, data)| WriteRange { + offset, + data: data.clone(), + }) + .collect() + } + + pub(super) fn write(&mut self, offset: u64, data: &[u8]) -> Result<(), i32> { + if data.is_empty() { + return Ok(()); + } + + let data_len = u64::try_from(data.len()).map_err(|_| libc::EINVAL)?; + let write_start = offset; + let write_end = offset.checked_add(data_len).ok_or(libc::EINVAL)?; + let mut start = write_start; + let mut end = write_end; + let mut existing_ranges = Vec::new(); + + if let Some((&prev_start, prev_data)) = self.ranges.range(..=write_start).next_back() { + let prev_end = prev_start + .checked_add(prev_data.len() as u64) + .ok_or(libc::EINVAL)?; + + if prev_end >= write_start { + let prev_data = prev_data.clone(); + self.ranges.remove(&prev_start); + self.bytes -= prev_data.len(); + + start = prev_start; + end = end.max(prev_end); + existing_ranges.push((prev_start, prev_data)); + } + } + + loop { + let next = self + .ranges + .range(start..) + .next() + .map(|(&next_start, next_data)| (next_start, next_data.clone())); + + let Some((next_start, next_data)) = next else { + break; + }; + + if next_start > end { + break; + } + + let next_end = next_start + .checked_add(next_data.len() as u64) + .ok_or(libc::EINVAL)?; + self.ranges.remove(&next_start); + self.bytes -= next_data.len(); + + end = end.max(next_end); + existing_ranges.push((next_start, next_data)); + } + + let mut merged = vec![0; (end - start) as usize]; + for (range_start, range_data) in existing_ranges { + let range_offset = (range_start - start) as usize; + merged[range_offset..range_offset + range_data.len()].copy_from_slice(&range_data); + } + + let write_offset = (write_start - start) as usize; + merged[write_offset..write_offset + data.len()].copy_from_slice(data); + + self.bytes += merged.len(); + self.ranges.insert(start, merged); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::{OpenFile, WriteBuffer}; + use agentfs_core::fs::{Stats, WriteRange, S_IFREG}; + use agentfs_core::{BoxedFile, File}; + use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }; + use tokio::runtime::Runtime; + + fn ranges(buffer: &WriteBuffer) -> Vec<(u64, Vec)> { + buffer + .ranges_for_flush() + .into_iter() + .map(|range| (range.offset, range.data)) + .collect() + } + + #[derive(Default)] + struct RecordingFile { + pwrite_calls: AtomicUsize, + pwrite_ranges_calls: AtomicUsize, + ranges: Mutex>, + } + + #[async_trait::async_trait] + impl File for RecordingFile { + async fn pread(&self, _offset: u64, _size: u64) -> agentfs_core::error::Result> { + Ok(Vec::new()) + } + + async fn pwrite(&self, _offset: u64, _data: &[u8]) -> agentfs_core::error::Result<()> { + self.pwrite_calls.fetch_add(1, Ordering::SeqCst); + Ok(()) + } + + async fn pwrite_ranges(&self, ranges: Vec) -> agentfs_core::error::Result<()> { + self.pwrite_ranges_calls.fetch_add(1, Ordering::SeqCst); + *self.ranges.lock().unwrap() = ranges; + Ok(()) + } + + async fn truncate(&self, _size: u64) -> agentfs_core::error::Result<()> { + Ok(()) + } + + async fn fsync(&self) -> agentfs_core::error::Result<()> { + Ok(()) + } + + async fn fstat(&self) -> agentfs_core::error::Result { + Ok(stats(1, S_IFREG | 0o644)) + } + } + + fn stats(ino: i64, mode: u32) -> Stats { + Stats { + ino, + mode, + nlink: 1, + uid: 1000, + gid: 1000, + size: 123, + atime: 1, + mtime: 2, + ctime: 3, + atime_nsec: 4, + mtime_nsec: 5, + ctime_nsec: 6, + rdev: 0, + } + } + + #[test] + fn write_buffer_merges_adjacent_ranges() { + let mut buffer = WriteBuffer::default(); + + buffer.write(0, b"hello").unwrap(); + buffer.write(5, b" world").unwrap(); + + assert_eq!(buffer.bytes(), 11); + assert_eq!(ranges(&buffer), vec![(0, b"hello world".to_vec())]); + } + + #[test] + fn write_buffer_overlays_overlapping_writes() { + let mut buffer = WriteBuffer::default(); + + buffer.write(0, b"abcdef").unwrap(); + buffer.write(2, b"ZZ").unwrap(); + + assert_eq!(buffer.bytes(), 6); + assert_eq!(ranges(&buffer), vec![(0, b"abZZef".to_vec())]); + } + + #[test] + fn write_buffer_overlays_following_range() { + let mut buffer = WriteBuffer::default(); + + buffer.write(10, b"abc").unwrap(); + buffer.write(8, b"ZZZZ").unwrap(); + + assert_eq!(buffer.bytes(), 5); + assert_eq!(ranges(&buffer), vec![(8, b"ZZZZc".to_vec())]); + } + + #[test] + fn write_buffer_bridges_two_existing_ranges() { + let mut buffer = WriteBuffer::default(); + + buffer.write(0, b"ab").unwrap(); + buffer.write(4, b"ef").unwrap(); + buffer.write(2, b"cd").unwrap(); + + assert_eq!(buffer.bytes(), 6); + assert_eq!(ranges(&buffer), vec![(0, b"abcdef".to_vec())]); + } + + #[test] + fn write_buffer_keeps_disjoint_ranges_ordered() { + let mut buffer = WriteBuffer::default(); + + buffer.write(10, b"tail").unwrap(); + buffer.write(0, b"head").unwrap(); + + assert_eq!(buffer.bytes(), 8); + assert_eq!( + ranges(&buffer), + vec![(0, b"head".to_vec()), (10, b"tail".to_vec())] + ); + } + + #[test] + fn write_buffer_rejects_offset_overflow() { + let mut buffer = WriteBuffer::default(); + + assert_eq!(buffer.write(u64::MAX, b"x"), Err(libc::EINVAL)); + assert!(buffer.is_empty()); + } + + #[test] + fn open_file_flushes_pending_writes_with_batch_api() { + let runtime = Runtime::new().unwrap(); + let recorder = Arc::new(RecordingFile::default()); + let file: BoxedFile = recorder.clone(); + let mut open_file = OpenFile::new(1, file); + + open_file.buffer_write(0, b"head").unwrap(); + open_file.buffer_write(10, b"tail").unwrap(); + open_file.flush_pending(&runtime).unwrap(); + + assert_eq!(recorder.pwrite_calls.load(Ordering::SeqCst), 0); + assert_eq!(recorder.pwrite_ranges_calls.load(Ordering::SeqCst), 1); + assert_eq!( + *recorder.ranges.lock().unwrap(), + vec![ + WriteRange { + offset: 0, + data: b"head".to_vec(), + }, + WriteRange { + offset: 10, + data: b"tail".to_vec(), + }, + ] + ); + assert!(open_file.pending.is_empty()); + } +} diff --git a/crates/agentfs-fuse/src/lib.rs b/crates/agentfs-fuse/src/lib.rs new file mode 100644 index 00000000..e72f0df3 --- /dev/null +++ b/crates/agentfs-fuse/src/lib.rs @@ -0,0 +1,31 @@ +//! Sealed Linux FUSE mount surface for AgentFS. +//! +//! The adopted FUSE transport and AgentFS adapter are private implementation +//! details. Callers receive only the mount entry point, mount options, and +//! session handle needed for lifecycle management (`mount`, +//! `FuseMountOptions`, `SessionHandle`). +//! +//! Owned invariants: +//! +//! - no-open/no-flush semantics: OPEN/RELEASE and close-time FLUSH answer +//! `ENOSYS` by default, per-inode resources live in a bounded LRU, and +//! cleanup is driven by FORGET traffic and eviction, never by RELEASE. +//! - POSIX lookup-reference accounting: every positive lookup reply retains +//! the backing inode reference it reports, and FORGET releases exactly +//! that count. +//! - Cache coherence: namespace mutations invalidate affected kernel and +//! adapter cache entries before the mutating reply is sent (epoch/reply +//! lock protocol in the adapter caches; audited in debug builds). +//! - Transport neutrality: the io_uring and `/dev/fuse` legs share request +//! semantics and bounded teardown; unmount joins all transport threads. +//! - No lock guard is held across an `.await` or `block_on` boundary. + +// The transport and adapter are Linux-only (/dev/fuse, io_uring, mount(2)); +// compile to an empty crate elsewhere so workspace-wide checks pass on macOS. +#![cfg(target_os = "linux")] + +mod adapter; +pub(crate) mod telemetry; +pub(crate) mod transport; + +pub use adapter::{mount, FuseMountOptions, SessionHandle}; diff --git a/crates/agentfs-fuse/src/telemetry.rs b/crates/agentfs-fuse/src/telemetry.rs new file mode 100644 index 00000000..2e26c37a --- /dev/null +++ b/crates/agentfs-fuse/src/telemetry.rs @@ -0,0 +1,428 @@ +//! FUSE-owned telemetry counters. +//! +//! The SDK owns the registry and core counter shapes. This module owns FUSE +//! adapter vocabulary and registers those sections with the SDK registry when +//! a FUSE session is mounted. + +use std::sync::OnceLock; +use std::time::Duration; + +pub(crate) use agentfs_core::telemetry::{ + is_enabled, record_base_fast_inode_invalidation, record_base_fast_open_eligible, + record_base_fast_open_keep_cache, record_base_fast_open_rejected, + record_base_fast_stale_rejection, record_negative_cache_hit, + record_negative_cache_invalidation, record_negative_cache_miss, timer, TimerGuard, +}; + +agentfs_core::telemetry::define_counters! { + pub(crate) static FUSE_COUNTERS: FuseCounters = "fuse" { + fuse_callback_count: Counter, + fuse_lookup_count: Counter, + fuse_getattr_count: Counter, + fuse_readdir_count: Counter, + fuse_readdir_plus_count: Counter, + fuse_open_count: Counter, + fuse_uring_requests: Counter, + fuse_read_count: Counter, + fuse_release_count: Counter, + fuse_write_count: Counter, + fuse_write_bytes: Sum, + fuse_flush_count: Counter, + fuse_flush_ranges: Sum, + fuse_flush_bytes: Sum, + fuse_noflush_enosys_replies: Counter, + fuse_pending_tail_drains: Counter, + fuse_noopen_enosys_replies: Counter, + fuse_ino_file_resolutions: Counter, + fuse_ino_file_upgrades: Counter, + fuse_sync_inval_inode_ok: Counter, + fuse_sync_inval_inode_err: Counter, + fuse_sync_inval_entry_ok: Counter, + fuse_sync_inval_entry_err: Counter, + fuse_sync_inval_latency_ns_total: Sum, + fuse_dispatch_wait: Timer, + fuse_adapter_lock_wait: Timer, + fuse_read_lane_wait: Timer, + fuse_write_lane_wait: Timer, + fuse_read_lane_max_concurrent: Max, + fuse_exclusive_fallback_count: Counter, + fuse_worker_queue_depth_peak: Max, + fuse_dispatch_inline_fallback: Counter, + fuse_dispatch_parallel_tasks: Counter, + fuse_dispatch_max_concurrent: Max, + fuse_readdirplus_auto_requested: Counter, + fuse_readdirplus_auto_enabled: Counter, + fuse_readdirplus_do_requested: Counter, + fuse_readdirplus_do_enabled: Counter, + fuse_readdirplus_unsupported: Counter, + fuse_keepcache_eligibility_drops: Counter, + fuse_adapter_entry_hits: Counter, + fuse_adapter_entry_misses: Counter, + fuse_adapter_attr_hits: Counter, + fuse_adapter_attr_misses: Counter, + fuse_adapter_negative_hits: Counter, + fuse_adapter_negative_misses: Counter, + fuse_adapter_inval_inode_notifications: Counter, + fuse_adapter_inval_entry_notifications: Counter, + fuse_op_lookup: Timer, + fuse_op_getattr: Timer, + fuse_op_setattr: Timer, + fuse_op_open: Timer, + fuse_op_create: Timer, + fuse_op_read: Timer, + fuse_op_write: Timer, + fuse_op_flush: Timer, + fuse_op_release: Timer, + fuse_op_readdirplus: Timer, + fuse_op_forget: Timer, + fuse_op_other: Timer, + } +} + +agentfs_core::telemetry::define_counters! { + pub(crate) static CONFIG_COUNTERS: ConfigCounters = "config" { + fuse_workers_configured: Gauge, + fuse_readdirplus_mode: Gauge, + fuse_ttl_entry_ms: Gauge, + fuse_ttl_attr_ms: Gauge, + fuse_ttl_neg_ms: Gauge, + fuse_writeback_cache_enabled: Gauge, + fuse_keepcache_enabled: Gauge, + } +} + +static REGISTERED: OnceLock<()> = OnceLock::new(); + +pub(crate) fn register_sections() { + REGISTERED.get_or_init(|| { + agentfs_core::telemetry::Registry::register(&FUSE_COUNTERS); + agentfs_core::telemetry::Registry::register(&CONFIG_COUNTERS); + }); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum FuseOpSlot { + Lookup, + GetAttr, + SetAttr, + Open, + Create, + Read, + Write, + Flush, + Release, + ReadDirPlus, + Forget, + Other, +} + +pub(crate) fn fuse_op_timer(slot: FuseOpSlot) -> TimerGuard { + let timer_slot = match slot { + FuseOpSlot::Lookup => &FUSE_COUNTERS.fuse_op_lookup, + FuseOpSlot::GetAttr => &FUSE_COUNTERS.fuse_op_getattr, + FuseOpSlot::SetAttr => &FUSE_COUNTERS.fuse_op_setattr, + FuseOpSlot::Open => &FUSE_COUNTERS.fuse_op_open, + FuseOpSlot::Create => &FUSE_COUNTERS.fuse_op_create, + FuseOpSlot::Read => &FUSE_COUNTERS.fuse_op_read, + FuseOpSlot::Write => &FUSE_COUNTERS.fuse_op_write, + FuseOpSlot::Flush => &FUSE_COUNTERS.fuse_op_flush, + FuseOpSlot::Release => &FUSE_COUNTERS.fuse_op_release, + FuseOpSlot::ReadDirPlus => &FUSE_COUNTERS.fuse_op_readdirplus, + FuseOpSlot::Forget => &FUSE_COUNTERS.fuse_op_forget, + FuseOpSlot::Other => &FUSE_COUNTERS.fuse_op_other, + }; + timer(timer_slot) +} + +fn record_fuse_callback() { + FUSE_COUNTERS.fuse_callback_count.increment(); +} + +pub(crate) fn record_fuse_lookup() { + if is_enabled() { + record_fuse_callback(); + FUSE_COUNTERS.fuse_lookup_count.increment(); + } +} + +pub(crate) fn record_fuse_getattr() { + if is_enabled() { + record_fuse_callback(); + FUSE_COUNTERS.fuse_getattr_count.increment(); + } +} + +pub(crate) fn record_fuse_readdir() { + if is_enabled() { + record_fuse_callback(); + FUSE_COUNTERS.fuse_readdir_count.increment(); + } +} + +pub(crate) fn record_fuse_readdir_plus() { + if is_enabled() { + record_fuse_callback(); + FUSE_COUNTERS.fuse_readdir_plus_count.increment(); + } +} + +pub(crate) fn record_fuse_open() { + if is_enabled() { + record_fuse_callback(); + FUSE_COUNTERS.fuse_open_count.increment(); + } +} + +pub(crate) fn record_fuse_uring_request() { + if is_enabled() { + FUSE_COUNTERS.fuse_uring_requests.increment(); + } +} + +pub(crate) fn record_fuse_read() { + if is_enabled() { + record_fuse_callback(); + FUSE_COUNTERS.fuse_read_count.increment(); + } +} + +pub(crate) fn record_fuse_release() { + if is_enabled() { + record_fuse_callback(); + FUSE_COUNTERS.fuse_release_count.increment(); + } +} + +pub(crate) fn record_fuse_write(bytes: u64) { + if is_enabled() { + record_fuse_callback(); + FUSE_COUNTERS.fuse_write_count.increment(); + FUSE_COUNTERS.fuse_write_bytes.add(bytes); + } +} + +pub(crate) fn record_fuse_flush(ranges: u64, bytes: u64) { + if is_enabled() { + FUSE_COUNTERS.fuse_flush_count.increment(); + FUSE_COUNTERS.fuse_flush_ranges.add(ranges); + FUSE_COUNTERS.fuse_flush_bytes.add(bytes); + } +} + +pub(crate) fn record_fuse_noflush_enosys_reply() { + if is_enabled() { + FUSE_COUNTERS.fuse_noflush_enosys_replies.increment(); + } +} + +pub(crate) fn record_fuse_pending_tail_drain() { + if is_enabled() { + FUSE_COUNTERS.fuse_pending_tail_drains.increment(); + } +} + +pub(crate) fn record_fuse_noopen_enosys_reply() { + if is_enabled() { + FUSE_COUNTERS.fuse_noopen_enosys_replies.increment(); + } +} + +pub(crate) fn record_fuse_ino_file_resolution() { + if is_enabled() { + FUSE_COUNTERS.fuse_ino_file_resolutions.increment(); + } +} + +pub(crate) fn record_fuse_ino_file_upgrade() { + if is_enabled() { + FUSE_COUNTERS.fuse_ino_file_upgrades.increment(); + } +} + +pub(crate) fn record_fuse_sync_inval_inode_ok() { + if is_enabled() { + FUSE_COUNTERS.fuse_sync_inval_inode_ok.increment(); + } +} + +pub(crate) fn record_fuse_sync_inval_inode_err() { + if is_enabled() { + FUSE_COUNTERS.fuse_sync_inval_inode_err.increment(); + } +} + +pub(crate) fn record_fuse_sync_inval_entry_ok() { + if is_enabled() { + FUSE_COUNTERS.fuse_sync_inval_entry_ok.increment(); + } +} + +pub(crate) fn record_fuse_sync_inval_entry_err() { + if is_enabled() { + FUSE_COUNTERS.fuse_sync_inval_entry_err.increment(); + } +} + +pub(crate) fn record_fuse_sync_inval_latency(duration: Duration) { + if is_enabled() { + FUSE_COUNTERS + .fuse_sync_inval_latency_ns_total + .add(duration.as_nanos() as u64); + } +} + +pub(crate) fn record_fuse_dispatch_wait(duration: Duration) { + if is_enabled() { + FUSE_COUNTERS.fuse_dispatch_wait.record(duration); + } +} + +pub(crate) fn set_fuse_workers_configured(workers: u64) { + if is_enabled() { + CONFIG_COUNTERS.fuse_workers_configured.set(workers); + } +} + +pub(crate) fn record_fuse_worker_queue_depth(depth: u64) { + if is_enabled() { + FUSE_COUNTERS.fuse_worker_queue_depth_peak.update(depth); + } +} + +pub(crate) fn record_fuse_dispatch_inline_fallback() { + if is_enabled() { + FUSE_COUNTERS.fuse_dispatch_inline_fallback.increment(); + } +} + +pub(crate) fn record_fuse_dispatch_parallel_task() { + if is_enabled() { + FUSE_COUNTERS.fuse_dispatch_parallel_tasks.increment(); + } +} + +pub(crate) fn record_fuse_dispatch_concurrency(concurrent: u64) { + if is_enabled() { + FUSE_COUNTERS + .fuse_dispatch_max_concurrent + .update(concurrent); + } +} + +pub(crate) fn record_fuse_readdirplus_auto_requested() { + if is_enabled() { + FUSE_COUNTERS.fuse_readdirplus_auto_requested.increment(); + } +} + +pub(crate) fn record_fuse_readdirplus_auto_enabled() { + if is_enabled() { + FUSE_COUNTERS.fuse_readdirplus_auto_enabled.increment(); + } +} + +pub(crate) fn record_fuse_readdirplus_do_requested() { + if is_enabled() { + FUSE_COUNTERS.fuse_readdirplus_do_requested.increment(); + } +} + +pub(crate) fn record_fuse_readdirplus_do_enabled() { + if is_enabled() { + FUSE_COUNTERS.fuse_readdirplus_do_enabled.increment(); + } +} + +pub(crate) fn record_fuse_readdirplus_unsupported() { + if is_enabled() { + FUSE_COUNTERS.fuse_readdirplus_unsupported.increment(); + } +} + +pub(crate) fn set_fuse_readdirplus_mode(mode: u64) { + if is_enabled() { + CONFIG_COUNTERS.fuse_readdirplus_mode.set(mode); + } +} + +pub(crate) fn set_fuse_ttl_ms(entry_ms: u64, attr_ms: u64, neg_ms: u64) { + if is_enabled() { + CONFIG_COUNTERS.fuse_ttl_entry_ms.set(entry_ms); + CONFIG_COUNTERS.fuse_ttl_attr_ms.set(attr_ms); + CONFIG_COUNTERS.fuse_ttl_neg_ms.set(neg_ms); + } +} + +pub(crate) fn set_fuse_writeback_cache_enabled(enabled: bool) { + if is_enabled() { + CONFIG_COUNTERS + .fuse_writeback_cache_enabled + .set(u64::from(enabled)); + } +} + +pub(crate) fn set_fuse_keepcache_enabled(enabled: bool) { + if is_enabled() { + CONFIG_COUNTERS + .fuse_keepcache_enabled + .set(u64::from(enabled)); + } +} + +pub(crate) fn record_fuse_keepcache_eligibility_drop() { + if is_enabled() { + FUSE_COUNTERS.fuse_keepcache_eligibility_drops.increment(); + } +} + +pub(crate) fn record_fuse_adapter_entry_hit() { + if is_enabled() { + FUSE_COUNTERS.fuse_adapter_entry_hits.increment(); + } +} + +pub(crate) fn record_fuse_adapter_entry_miss() { + if is_enabled() { + FUSE_COUNTERS.fuse_adapter_entry_misses.increment(); + } +} + +pub(crate) fn record_fuse_adapter_attr_hit() { + if is_enabled() { + FUSE_COUNTERS.fuse_adapter_attr_hits.increment(); + } +} + +pub(crate) fn record_fuse_adapter_attr_miss() { + if is_enabled() { + FUSE_COUNTERS.fuse_adapter_attr_misses.increment(); + } +} + +pub(crate) fn record_fuse_adapter_negative_hit() { + if is_enabled() { + FUSE_COUNTERS.fuse_adapter_negative_hits.increment(); + } +} + +pub(crate) fn record_fuse_adapter_negative_miss() { + if is_enabled() { + FUSE_COUNTERS.fuse_adapter_negative_misses.increment(); + } +} + +pub(crate) fn record_fuse_adapter_inval_inode_notification() { + if is_enabled() { + FUSE_COUNTERS + .fuse_adapter_inval_inode_notifications + .increment(); + } +} + +pub(crate) fn record_fuse_adapter_inval_entry_notification() { + if is_enabled() { + FUSE_COUNTERS + .fuse_adapter_inval_entry_notifications + .increment(); + } +} diff --git a/cli/src/fuser/channel.rs b/crates/agentfs-fuse/src/transport/channel.rs similarity index 94% rename from cli/src/fuser/channel.rs rename to crates/agentfs-fuse/src/transport/channel.rs index bfb230fb..d77e4bb1 100644 --- a/cli/src/fuser/channel.rs +++ b/crates/agentfs-fuse/src/transport/channel.rs @@ -14,7 +14,7 @@ use super::reply::ReplySender; /// A raw communication channel to the FUSE kernel driver #[derive(Debug)] -pub struct Channel { +pub(crate) struct Channel { device: Arc, } @@ -33,7 +33,7 @@ impl Channel { } /// Receives data up to the capacity of the given buffer (can block). - pub fn receive(&self, buffer: &mut [u8]) -> io::Result { + pub(crate) fn receive(&self, buffer: &mut [u8]) -> io::Result { let rc = unsafe { libc::read( self.device.as_raw_fd(), @@ -56,7 +56,7 @@ impl Channel { /// Returns a sender object for this channel. The sender object can be /// used to send to the channel. Multiple sender objects can be used /// and they can safely be sent to other threads. - pub fn sender(&self) -> ChannelSender { + pub(crate) fn sender(&self) -> ChannelSender { ChannelSender::Fd { device: self.device.clone(), } @@ -66,7 +66,7 @@ impl Channel { /// Reply target for a FUSE request: either the classic /dev/fuse writev path /// or a fuse-over-io_uring ring entry commit. #[derive(Clone, Debug)] -pub enum ChannelSender { +pub(crate) enum ChannelSender { Fd { device: Arc, }, diff --git a/cli/src/fuser/deferred_notify.rs b/crates/agentfs-fuse/src/transport/deferred_notify.rs similarity index 87% rename from cli/src/fuser/deferred_notify.rs rename to crates/agentfs-fuse/src/transport/deferred_notify.rs index b796c127..00808366 100644 --- a/cli/src/fuser/deferred_notify.rs +++ b/crates/agentfs-fuse/src/transport/deferred_notify.rs @@ -1,12 +1,12 @@ -use log::debug; use std::{ ffi::{OsStr, OsString}, sync::mpsc, }; +use tracing::debug; /// A queued invalidation operation to be flushed by the notify thread. #[derive(Debug)] -pub enum NotifyOp { +pub(crate) enum NotifyOp { InvalEntry { parent: u64, name: OsString }, InvalInode { ino: u64, offset: i64, len: i64 }, } @@ -24,7 +24,7 @@ pub enum NotifyOp { /// to a dedicated background thread that writes to /dev/fuse independently /// of the session loop. #[derive(Debug, Clone)] -pub struct DeferredNotifier { +pub(crate) struct DeferredNotifier { tx: mpsc::Sender, } @@ -33,7 +33,7 @@ impl DeferredNotifier { Self { tx } } - pub fn inval_entry(&self, parent: u64, name: &OsStr) { + pub(crate) fn inval_entry(&self, parent: u64, name: &OsStr) { if let Err(e) = self.tx.send(NotifyOp::InvalEntry { parent, name: name.to_os_string(), @@ -42,7 +42,7 @@ impl DeferredNotifier { } } - pub fn inval_inode(&self, ino: u64, offset: i64, len: i64) { + pub(crate) fn inval_inode(&self, ino: u64, offset: i64, len: i64) { if let Err(e) = self.tx.send(NotifyOp::InvalInode { ino, offset, len }) { debug!("deferred inval_inode send failed (notify thread gone?): {e}"); } diff --git a/cli/src/fuser/ll/argument.rs b/crates/agentfs-fuse/src/transport/ll/argument.rs similarity index 90% rename from cli/src/fuser/ll/argument.rs rename to crates/agentfs-fuse/src/transport/ll/argument.rs index 510a931e..07005854 100644 --- a/cli/src/fuser/ll/argument.rs +++ b/crates/agentfs-fuse/src/transport/ll/argument.rs @@ -8,30 +8,30 @@ use std::os::unix::ffi::OsStrExt; use zerocopy::{FromBytes, Immutable, KnownLayout}; /// An iterator that can be used to fetch typed arguments from a byte slice. -pub struct ArgumentIterator<'a> { +pub(crate) struct ArgumentIterator<'a> { data: &'a [u8], } impl<'a> ArgumentIterator<'a> { /// Create a new argument iterator for the given byte slice. - pub fn new(data: &'a [u8]) -> ArgumentIterator<'a> { + pub(crate) fn new(data: &'a [u8]) -> ArgumentIterator<'a> { ArgumentIterator { data } } /// Returns the size of the remaining data. - pub fn len(&self) -> usize { + pub(crate) fn len(&self) -> usize { self.data.len() } /// Fetch a slice of all remaining bytes. - pub fn fetch_all(&mut self) -> &'a [u8] { + pub(crate) fn fetch_all(&mut self) -> &'a [u8] { let bytes = self.data; self.data = &[]; bytes } /// Fetch a typed argument. Returns `None` if there's not enough data left. - pub fn fetch(&mut self) -> Option<&'a T> { + pub(crate) fn fetch(&mut self) -> Option<&'a T> { match zerocopy::Ref::<_, T>::from_prefix(self.data) { Err(_err) => { // TODO: do something with _err @@ -53,7 +53,10 @@ impl<'a> ArgumentIterator<'a> { } /// Fetch a slice of typed of arguments. Returns `None` if there's not enough data left. - pub fn fetch_slice(&mut self, count: usize) -> Option<&'a [T]> { + pub(crate) fn fetch_slice( + &mut self, + count: usize, + ) -> Option<&'a [T]> { match zerocopy::Ref::<_, [T]>::from_prefix_with_elems(self.data, count) { Err(_err) => { // TODO: do something with _err @@ -76,7 +79,7 @@ impl<'a> ArgumentIterator<'a> { /// Fetch a (zero-terminated) string (can be non-utf8). Returns `None` if there's not enough /// data left or no zero-termination could be found. - pub fn fetch_str(&mut self) -> Option<&'a OsStr> { + pub(crate) fn fetch_str(&mut self) -> Option<&'a OsStr> { let len = memchr::memchr(0, self.data)?; let (out, rest) = self.data.split_at(len); self.data = &rest[1..]; @@ -85,7 +88,7 @@ impl<'a> ArgumentIterator<'a> { } #[cfg(test)] -pub mod tests { +pub(crate) mod tests { use super::super::test::AlignedData; use super::*; diff --git a/crates/agentfs-fuse/src/transport/ll/fuse_abi.rs b/crates/agentfs-fuse/src/transport/ll/fuse_abi.rs new file mode 100644 index 00000000..cddc0251 --- /dev/null +++ b/crates/agentfs-fuse/src/transport/ll/fuse_abi.rs @@ -0,0 +1,797 @@ +//! FUSE kernel interface. +//! +//! Types and definitions used for communication between the kernel driver and the userspace +//! part of a FUSE filesystem. Since the kernel driver may be installed independently, the ABI +//! interface is versioned and capabilities are exchanged during the initialization (mounting) +//! of a filesystem. +//! +//! libfuse (Linux): +//! - supports ABI 7.8 since FUSE 2.6.0 +//! - supports ABI 7.12 since FUSE 2.8.0 +//! - supports ABI 7.18 since FUSE 2.9.0 +//! - supports ABI 7.19 since FUSE 2.9.1 +//! - supports ABI 7.26 since FUSE 3.0.0 +//! +//! Items without a version annotation are valid with ABI 7.8 and later + +#![warn(missing_debug_implementations)] +#![allow(missing_docs)] + +use consts::{FATTR_ATIME_NOW, FATTR_MTIME_NOW}; +use std::convert::TryFrom; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +pub(crate) const FUSE_KERNEL_VERSION: u32 = 7; +pub(crate) const FUSE_KERNEL_MINOR_VERSION: u32 = 42; + +#[allow( + dead_code, + reason = "kept as part of the adopted Linux FUSE ABI surface" +)] +pub(crate) const FUSE_ROOT_ID: u64 = 1; + +#[repr(C)] +#[derive(Debug, IntoBytes, Clone, Copy, KnownLayout, Immutable)] +pub(crate) struct fuse_attr { + pub(crate) ino: u64, + pub(crate) size: u64, + pub(crate) blocks: u64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed + // to match stat.st_atime + pub(crate) atime: i64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed + // to match stat.st_mtime + pub(crate) mtime: i64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed + // to match stat.st_ctime + pub(crate) ctime: i64, + pub(crate) atimensec: u32, + pub(crate) mtimensec: u32, + pub(crate) ctimensec: u32, + pub(crate) mode: u32, + pub(crate) nlink: u32, + pub(crate) uid: u32, + pub(crate) gid: u32, + pub(crate) rdev: u32, + pub(crate) blksize: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_kstatfs { + pub(crate) blocks: u64, // Total blocks (in units of frsize) + pub(crate) bfree: u64, // Free blocks + pub(crate) bavail: u64, // Free blocks for unprivileged users + pub(crate) files: u64, // Total inodes + pub(crate) ffree: u64, // Free inodes + pub(crate) bsize: u32, // Filesystem block size + pub(crate) namelen: u32, // Maximum filename length + pub(crate) frsize: u32, // Fundamental file system block size + pub(crate) padding: u32, + pub(crate) spare: [u32; 6], +} + +#[repr(C)] +#[derive(Debug, IntoBytes, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_file_lock { + pub(crate) start: u64, + pub(crate) end: u64, + // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is treated as signed + pub(crate) typ: i32, + pub(crate) pid: u32, +} + +#[allow(dead_code, reason = "kept as a complete Linux FUSE ABI constant table")] +pub(crate) mod consts { + // Bitmasks for fuse_setattr_in.valid + pub(crate) const FATTR_MODE: u32 = 1 << 0; + pub(crate) const FATTR_UID: u32 = 1 << 1; + pub(crate) const FATTR_GID: u32 = 1 << 2; + pub(crate) const FATTR_SIZE: u32 = 1 << 3; + pub(crate) const FATTR_ATIME: u32 = 1 << 4; + pub(crate) const FATTR_MTIME: u32 = 1 << 5; + pub(crate) const FATTR_FH: u32 = 1 << 6; + pub(crate) const FATTR_ATIME_NOW: u32 = 1 << 7; + pub(crate) const FATTR_MTIME_NOW: u32 = 1 << 8; + pub(crate) const FATTR_LOCKOWNER: u32 = 1 << 9; + pub(crate) const FATTR_CTIME: u32 = 1 << 10; + + // Flags returned by the open request + pub(crate) const FOPEN_DIRECT_IO: u32 = 1 << 0; // bypass page cache for this open file + pub(crate) const FOPEN_KEEP_CACHE: u32 = 1 << 1; // don't invalidate the data cache on open + pub(crate) const FOPEN_NONSEEKABLE: u32 = 1 << 2; // the file is not seekable + pub(crate) const FOPEN_CACHE_DIR: u32 = 1 << 3; // allow caching this directory + pub(crate) const FOPEN_STREAM: u32 = 1 << 4; // the file is stream-like (no file position at all) + // Init request/reply flags + pub(crate) const FUSE_ASYNC_READ: u64 = 1 << 0; // asynchronous read requests + pub(crate) const FUSE_POSIX_LOCKS: u64 = 1 << 1; // remote locking for POSIX file locks + pub(crate) const FUSE_FILE_OPS: u64 = 1 << 2; // kernel sends file handle for fstat, etc... + pub(crate) const FUSE_ATOMIC_O_TRUNC: u64 = 1 << 3; // handles the O_TRUNC open flag in the filesystem + pub(crate) const FUSE_EXPORT_SUPPORT: u64 = 1 << 4; // filesystem handles lookups of "." and ".." + pub(crate) const FUSE_BIG_WRITES: u64 = 1 << 5; // filesystem can handle write size larger than 4kB + pub(crate) const FUSE_DONT_MASK: u64 = 1 << 6; // don't apply umask to file mode on create operations + pub(crate) const FUSE_SPLICE_WRITE: u64 = 1 << 7; // kernel supports splice write on the device + pub(crate) const FUSE_SPLICE_MOVE: u64 = 1 << 8; // kernel supports splice move on the device + pub(crate) const FUSE_SPLICE_READ: u64 = 1 << 9; // kernel supports splice read on the device + pub(crate) const FUSE_FLOCK_LOCKS: u64 = 1 << 10; // remote locking for flock-style file locks + pub(crate) const FUSE_HAS_IOCTL_DIR: u64 = 1 << 11; // kernel supports ioctl on directories + pub(crate) const FUSE_AUTO_INVAL_DATA: u64 = 1 << 12; // automatically invalidate cached pages + pub(crate) const FUSE_DO_READDIRPLUS: u64 = 1 << 13; // do READDIRPLUS (READDIR+LOOKUP in one) + pub(crate) const FUSE_READDIRPLUS_AUTO: u64 = 1 << 14; // adaptive readdirplus + pub(crate) const FUSE_ASYNC_DIO: u64 = 1 << 15; // asynchronous direct I/O submission + pub(crate) const FUSE_WRITEBACK_CACHE: u64 = 1 << 16; // use writeback cache for buffered writes + pub(crate) const FUSE_NO_OPEN_SUPPORT: u64 = 1 << 17; // kernel supports zero-message opens + pub(crate) const FUSE_PARALLEL_DIROPS: u64 = 1 << 18; // allow parallel lookups and readdir + pub(crate) const FUSE_HANDLE_KILLPRIV: u64 = 1 << 19; // fs handles killing suid/sgid/cap on write/chown/trunc + pub(crate) const FUSE_POSIX_ACL: u64 = 1 << 20; // filesystem supports posix acls + pub(crate) const FUSE_ABORT_ERROR: u64 = 1 << 21; // reading the device after abort returns ECONNABORTED + pub(crate) const FUSE_MAX_PAGES: u64 = 1 << 22; // init_out.max_pages contains the max number of req pages + pub(crate) const FUSE_CACHE_SYMLINKS: u64 = 1 << 23; // cache READLINK responses + pub(crate) const FUSE_NO_OPENDIR_SUPPORT: u64 = 1 << 24; // kernel supports zero-message opendir + pub(crate) const FUSE_EXPLICIT_INVAL_DATA: u64 = 1 << 25; // only invalidate cached pages on explicit request + pub(crate) const FUSE_INIT_EXT: u64 = 1 << 30; // extended fuse_init_in request + pub(crate) const FUSE_INIT_RESERVED: u64 = 1 << 31; // reserved, do not use + pub(crate) const FUSE_OVER_IO_URING: u64 = 1 << 41; // client supports fuse-over-io-uring + + // CUSE init request/reply flags + pub(crate) const CUSE_UNRESTRICTED_IOCTL: u32 = 1 << 0; // use unrestricted ioctl + + // Release flags + pub(crate) const FUSE_RELEASE_FLUSH: u32 = 1 << 0; + pub(crate) const FUSE_RELEASE_FLOCK_UNLOCK: u32 = 1 << 1; + + // Getattr flags + pub(crate) const FUSE_GETATTR_FH: u32 = 1 << 0; + + // Lock flags + pub(crate) const FUSE_LK_FLOCK: u32 = 1 << 0; + + // Write flags + pub(crate) const FUSE_WRITE_CACHE: u32 = 1 << 0; // delayed write from page cache, file handle is guessed + pub(crate) const FUSE_WRITE_LOCKOWNER: u32 = 1 << 1; // lock_owner field is valid + pub(crate) const FUSE_WRITE_KILL_PRIV: u32 = 1 << 2; // kill suid and sgid bits + + // Read flags + pub(crate) const FUSE_READ_LOCKOWNER: u32 = 1 << 1; + + // IOCTL flags + pub(crate) const FUSE_IOCTL_COMPAT: u32 = 1 << 0; // 32bit compat ioctl on 64bit machine + pub(crate) const FUSE_IOCTL_UNRESTRICTED: u32 = 1 << 1; // not restricted to well-formed ioctls, retry allowed + pub(crate) const FUSE_IOCTL_RETRY: u32 = 1 << 2; // retry with new iovecs + pub(crate) const FUSE_IOCTL_32BIT: u32 = 1 << 3; // 32bit ioctl + pub(crate) const FUSE_IOCTL_DIR: u32 = 1 << 4; // is a directory + pub(crate) const FUSE_IOCTL_COMPAT_X32: u32 = 1 << 5; // x32 compat ioctl on 64bit machine (64bit time_t) + pub(crate) const FUSE_IOCTL_MAX_IOV: u32 = 256; // maximum of in_iovecs + out_iovecs + + // Poll flags + pub(crate) const FUSE_POLL_SCHEDULE_NOTIFY: u32 = 1 << 0; // request poll notify + + // fsync flags + pub(crate) const FUSE_FSYNC_FDATASYNC: u32 = 1 << 0; // Sync data only, not metadata + + // The read buffer is required to be at least 8k, but may be much larger + pub(crate) const FUSE_MIN_READ_BUFFER: usize = 8192; +} + +/// Invalid opcode error. +#[derive(Debug)] +pub(crate) struct InvalidOpcodeError; + +#[repr(C)] +#[derive(Debug)] +#[allow(non_camel_case_types)] +pub(crate) enum fuse_opcode { + FUSE_LOOKUP = 1, + FUSE_FORGET = 2, // no reply + FUSE_GETATTR = 3, + FUSE_SETATTR = 4, + FUSE_READLINK = 5, + FUSE_SYMLINK = 6, + FUSE_MKNOD = 8, + FUSE_MKDIR = 9, + FUSE_UNLINK = 10, + FUSE_RMDIR = 11, + FUSE_RENAME = 12, + FUSE_LINK = 13, + FUSE_OPEN = 14, + FUSE_READ = 15, + FUSE_WRITE = 16, + FUSE_STATFS = 17, + FUSE_RELEASE = 18, + FUSE_FSYNC = 20, + FUSE_SETXATTR = 21, + FUSE_GETXATTR = 22, + FUSE_LISTXATTR = 23, + FUSE_REMOVEXATTR = 24, + FUSE_FLUSH = 25, + FUSE_INIT = 26, + FUSE_OPENDIR = 27, + FUSE_READDIR = 28, + FUSE_RELEASEDIR = 29, + FUSE_FSYNCDIR = 30, + FUSE_GETLK = 31, + FUSE_SETLK = 32, + FUSE_SETLKW = 33, + FUSE_ACCESS = 34, + FUSE_CREATE = 35, + FUSE_INTERRUPT = 36, + FUSE_BMAP = 37, + FUSE_DESTROY = 38, + FUSE_IOCTL = 39, + FUSE_POLL = 40, + FUSE_BATCH_FORGET = 42, + FUSE_FALLOCATE = 43, + FUSE_READDIRPLUS = 44, + FUSE_RENAME2 = 45, + FUSE_LSEEK = 46, + FUSE_COPY_FILE_RANGE = 47, +} + +impl TryFrom for fuse_opcode { + type Error = InvalidOpcodeError; + + fn try_from(n: u32) -> Result { + match n { + 1 => Ok(fuse_opcode::FUSE_LOOKUP), + 2 => Ok(fuse_opcode::FUSE_FORGET), + 3 => Ok(fuse_opcode::FUSE_GETATTR), + 4 => Ok(fuse_opcode::FUSE_SETATTR), + 5 => Ok(fuse_opcode::FUSE_READLINK), + 6 => Ok(fuse_opcode::FUSE_SYMLINK), + 8 => Ok(fuse_opcode::FUSE_MKNOD), + 9 => Ok(fuse_opcode::FUSE_MKDIR), + 10 => Ok(fuse_opcode::FUSE_UNLINK), + 11 => Ok(fuse_opcode::FUSE_RMDIR), + 12 => Ok(fuse_opcode::FUSE_RENAME), + 13 => Ok(fuse_opcode::FUSE_LINK), + 14 => Ok(fuse_opcode::FUSE_OPEN), + 15 => Ok(fuse_opcode::FUSE_READ), + 16 => Ok(fuse_opcode::FUSE_WRITE), + 17 => Ok(fuse_opcode::FUSE_STATFS), + 18 => Ok(fuse_opcode::FUSE_RELEASE), + 20 => Ok(fuse_opcode::FUSE_FSYNC), + 21 => Ok(fuse_opcode::FUSE_SETXATTR), + 22 => Ok(fuse_opcode::FUSE_GETXATTR), + 23 => Ok(fuse_opcode::FUSE_LISTXATTR), + 24 => Ok(fuse_opcode::FUSE_REMOVEXATTR), + 25 => Ok(fuse_opcode::FUSE_FLUSH), + 26 => Ok(fuse_opcode::FUSE_INIT), + 27 => Ok(fuse_opcode::FUSE_OPENDIR), + 28 => Ok(fuse_opcode::FUSE_READDIR), + 29 => Ok(fuse_opcode::FUSE_RELEASEDIR), + 30 => Ok(fuse_opcode::FUSE_FSYNCDIR), + 31 => Ok(fuse_opcode::FUSE_GETLK), + 32 => Ok(fuse_opcode::FUSE_SETLK), + 33 => Ok(fuse_opcode::FUSE_SETLKW), + 34 => Ok(fuse_opcode::FUSE_ACCESS), + 35 => Ok(fuse_opcode::FUSE_CREATE), + 36 => Ok(fuse_opcode::FUSE_INTERRUPT), + 37 => Ok(fuse_opcode::FUSE_BMAP), + 38 => Ok(fuse_opcode::FUSE_DESTROY), + 39 => Ok(fuse_opcode::FUSE_IOCTL), + 40 => Ok(fuse_opcode::FUSE_POLL), + 42 => Ok(fuse_opcode::FUSE_BATCH_FORGET), + 43 => Ok(fuse_opcode::FUSE_FALLOCATE), + 44 => Ok(fuse_opcode::FUSE_READDIRPLUS), + 45 => Ok(fuse_opcode::FUSE_RENAME2), + 46 => Ok(fuse_opcode::FUSE_LSEEK), + 47 => Ok(fuse_opcode::FUSE_COPY_FILE_RANGE), + _ => Err(InvalidOpcodeError), + } + } +} + +/// Invalid notify code error. +#[derive(Debug)] +pub(crate) struct InvalidNotifyCodeError; + +#[repr(C)] +#[derive(Debug)] +#[allow(non_camel_case_types)] +pub(crate) enum fuse_notify_code { + FUSE_POLL = 1, + FUSE_NOTIFY_INVAL_INODE = 2, + FUSE_NOTIFY_INVAL_ENTRY = 3, + FUSE_NOTIFY_STORE = 4, + FUSE_NOTIFY_RETRIEVE = 5, + FUSE_NOTIFY_DELETE = 6, +} + +impl TryFrom for fuse_notify_code { + type Error = InvalidNotifyCodeError; + + fn try_from(n: u32) -> Result { + match n { + 1 => Ok(fuse_notify_code::FUSE_POLL), + 2 => Ok(fuse_notify_code::FUSE_NOTIFY_INVAL_INODE), + 3 => Ok(fuse_notify_code::FUSE_NOTIFY_INVAL_ENTRY), + 4 => Ok(fuse_notify_code::FUSE_NOTIFY_STORE), + 5 => Ok(fuse_notify_code::FUSE_NOTIFY_RETRIEVE), + 6 => Ok(fuse_notify_code::FUSE_NOTIFY_DELETE), + + _ => Err(InvalidNotifyCodeError), + } + } +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_entry_out { + pub(crate) nodeid: u64, + pub(crate) generation: u64, + pub(crate) entry_valid: u64, + pub(crate) attr_valid: u64, + pub(crate) entry_valid_nsec: u32, + pub(crate) attr_valid_nsec: u32, + pub(crate) attr: fuse_attr, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_forget_in { + pub(crate) nlookup: u64, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_forget_one { + pub(crate) nodeid: u64, + pub(crate) nlookup: u64, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_batch_forget_in { + pub(crate) count: u32, + pub(crate) dummy: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_getattr_in { + pub(crate) getattr_flags: u32, + pub(crate) dummy: u32, + pub(crate) fh: u64, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_attr_out { + pub(crate) attr_valid: u64, + pub(crate) attr_valid_nsec: u32, + pub(crate) dummy: u32, + pub(crate) attr: fuse_attr, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_mknod_in { + pub(crate) mode: u32, + pub(crate) rdev: u32, + pub(crate) umask: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_mkdir_in { + pub(crate) mode: u32, + pub(crate) umask: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_rename_in { + pub(crate) newdir: u64, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_rename2_in { + pub(crate) newdir: u64, + pub(crate) flags: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_link_in { + pub(crate) oldnodeid: u64, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_setattr_in { + pub(crate) valid: u32, + pub(crate) padding: u32, + pub(crate) fh: u64, + pub(crate) size: u64, + pub(crate) lock_owner: u64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed + // to match stat.st_atime + pub(crate) atime: i64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed + // to match stat.st_mtime + pub(crate) mtime: i64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed + // to match stat.st_ctime + pub(crate) ctime: i64, + pub(crate) atimensec: u32, + pub(crate) mtimensec: u32, + pub(crate) ctimensec: u32, + pub(crate) mode: u32, + pub(crate) unused4: u32, + pub(crate) uid: u32, + pub(crate) gid: u32, + pub(crate) unused5: u32, +} + +impl fuse_setattr_in { + pub(crate) fn atime_now(&self) -> bool { + self.valid & FATTR_ATIME_NOW != 0 + } + + pub(crate) fn mtime_now(&self) -> bool { + self.valid & FATTR_MTIME_NOW != 0 + } +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_open_in { + // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast + // to an i32 when invoking the filesystem's open method and this matches the open() syscall + pub(crate) flags: i32, + pub(crate) unused: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_create_in { + // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast + // to an i32 when invoking the filesystem's create method and this matches the open() syscall + pub(crate) flags: i32, + pub(crate) mode: u32, + pub(crate) umask: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_create_out(pub(crate) fuse_entry_out, pub(crate) fuse_open_out); + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_open_out { + pub(crate) fh: u64, + pub(crate) open_flags: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_release_in { + pub(crate) fh: u64, + // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast + // to an i32 when invoking the filesystem's read method + pub(crate) flags: i32, + pub(crate) release_flags: u32, + pub(crate) lock_owner: u64, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_flush_in { + pub(crate) fh: u64, + pub(crate) unused: u32, + pub(crate) padding: u32, + pub(crate) lock_owner: u64, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_read_in { + pub(crate) fh: u64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is then cast + // to an i64 when invoking the filesystem's read method + pub(crate) offset: i64, + pub(crate) size: u32, + pub(crate) read_flags: u32, + pub(crate) lock_owner: u64, + // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast + // to an i32 when invoking the filesystem's read method + pub(crate) flags: i32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_write_in { + pub(crate) fh: u64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is then cast + // to an i64 when invoking the filesystem's write method + pub(crate) offset: i64, + pub(crate) size: u32, + pub(crate) write_flags: u32, + pub(crate) lock_owner: u64, + // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast + // to an i32 when invoking the filesystem's read method + pub(crate) flags: i32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_write_out { + pub(crate) size: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_statfs_out { + pub(crate) st: fuse_kstatfs, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_fsync_in { + pub(crate) fh: u64, + pub(crate) fsync_flags: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_setxattr_in { + pub(crate) size: u32, + // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast + // to an i32 when invoking the filesystem's setxattr method + pub(crate) flags: i32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_getxattr_in { + pub(crate) size: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +#[allow( + dead_code, + reason = "reply shape retained for default-ENOSYS xattr dispatch" +)] +pub(crate) struct fuse_getxattr_out { + pub(crate) size: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_lk_in { + pub(crate) fh: u64, + pub(crate) owner: u64, + pub(crate) lk: fuse_file_lock, + pub(crate) lk_flags: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +#[allow( + dead_code, + reason = "reply shape retained for default-ENOSYS lock dispatch" +)] +pub(crate) struct fuse_lk_out { + pub(crate) lk: fuse_file_lock, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_access_in { + // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is then cast + // to an i32 when invoking the filesystem's access method + pub(crate) mask: i32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_init_in { + pub(crate) major: u32, + pub(crate) minor: u32, + pub(crate) max_readahead: u32, + pub(crate) flags: u32, + pub(crate) flags2: u32, + pub(crate) unused: [u32; 11], +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_init_out { + pub(crate) major: u32, + pub(crate) minor: u32, + pub(crate) max_readahead: u32, + pub(crate) flags: u32, + pub(crate) max_background: u16, + pub(crate) congestion_threshold: u16, + pub(crate) max_write: u32, + pub(crate) time_gran: u32, + pub(crate) max_pages: u16, + pub(crate) unused2: u16, + pub(crate) flags2: u32, + pub(crate) reserved: [u32; 7], +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_interrupt_in { + pub(crate) unique: u64, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_bmap_in { + pub(crate) block: u64, + pub(crate) blocksize: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +#[allow( + dead_code, + reason = "reply shape retained for default-ENOSYS bmap dispatch" +)] +pub(crate) struct fuse_bmap_out { + pub(crate) block: u64, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_ioctl_in { + pub(crate) fh: u64, + pub(crate) flags: u32, + pub(crate) cmd: u32, + pub(crate) arg: u64, // TODO: this is currently unused, but is defined as a void* in libfuse + pub(crate) in_size: u32, + pub(crate) out_size: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +#[allow( + dead_code, + reason = "reply shape retained for default-ENOSYS ioctl dispatch" +)] +pub(crate) struct fuse_ioctl_out { + pub(crate) result: i32, + pub(crate) flags: u32, + pub(crate) in_iovs: u32, + pub(crate) out_iovs: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_poll_in { + pub(crate) fh: u64, + pub(crate) kh: u64, + pub(crate) flags: u32, + pub(crate) events: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +#[allow( + dead_code, + reason = "reply shape retained for default-ENOSYS poll dispatch" +)] +pub(crate) struct fuse_poll_out { + pub(crate) revents: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_fallocate_in { + pub(crate) fh: u64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed + pub(crate) offset: i64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed + pub(crate) length: i64, + // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is treated as signed + pub(crate) mode: i32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_in_header { + pub(crate) len: u32, + pub(crate) opcode: u32, + pub(crate) unique: u64, + pub(crate) nodeid: u64, + pub(crate) uid: u32, + pub(crate) gid: u32, + pub(crate) pid: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_out_header { + pub(crate) len: u32, + pub(crate) error: i32, + pub(crate) unique: u64, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_dirent { + pub(crate) ino: u64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed + pub(crate) off: i64, + pub(crate) namelen: u32, + pub(crate) typ: u32, + // followed by name of namelen bytes +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_direntplus { + pub(crate) entry_out: fuse_entry_out, + pub(crate) dirent: fuse_dirent, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_notify_inval_inode_out { + pub(crate) ino: u64, + pub(crate) off: i64, + pub(crate) len: i64, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_notify_inval_entry_out { + pub(crate) parent: u64, + pub(crate) namelen: u32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_lseek_in { + pub(crate) fh: u64, + pub(crate) offset: i64, + // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is treated as signed + pub(crate) whence: i32, + pub(crate) padding: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, KnownLayout, Immutable)] +#[allow( + dead_code, + reason = "reply shape retained for default-ENOSYS lseek dispatch" +)] +pub(crate) struct fuse_lseek_out { + pub(crate) offset: i64, +} + +#[repr(C)] +#[derive(Debug, FromBytes, KnownLayout, Immutable)] +pub(crate) struct fuse_copy_file_range_in { + pub(crate) fh_in: u64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed + pub(crate) off_in: i64, + pub(crate) nodeid_out: u64, + pub(crate) fh_out: u64, + // NOTE: this field is defined as u64 in fuse_kernel.h in libfuse. However, it is treated as signed + pub(crate) off_out: i64, + pub(crate) len: u64, + pub(crate) flags: u64, +} diff --git a/crates/agentfs-fuse/src/transport/ll/mod.rs b/crates/agentfs-fuse/src/transport/ll/mod.rs new file mode 100644 index 00000000..8ebca5c2 --- /dev/null +++ b/crates/agentfs-fuse/src/transport/ll/mod.rs @@ -0,0 +1,303 @@ +//! Low-level kernel communication. + +mod argument; +pub(crate) mod fuse_abi; +pub(crate) mod notify; +pub(crate) mod reply; +pub(crate) mod request; + +use std::{convert::TryInto, num::NonZeroI32, time::SystemTime}; + +pub(crate) use reply::Response; +pub(crate) use request::{ + AnyRequest, FileHandle, INodeNo, Lock, Operation, Request, RequestId, Version, +}; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +/// Possible input arguments for atime & mtime, which can either be set to a specified time, +/// or to the current time +pub(crate) enum TimeOrNow { + /// Specific time provided + SpecificTime(SystemTime), + /// Current time + Now, +} + +macro_rules! errno { + ($x: expr_2021) => { + Errno(unsafe { + // This is a static assertion that the constant $x is > 0 + const _X: [(); 0 - !{ + const ASSERT: bool = ($x > 0); + ASSERT + } as usize] = []; + // Which makes this safe + NonZeroI32::new_unchecked($x) + }) + }; +} + +macro_rules! no_xattr_doc { + () => {"Use this as an error return from getxattr/removexattr to indicate that the xattr doesn't exist. This resolves to the appropriate platform-specific error code."} +} + +/// Represents an error code to be returned to the caller +#[derive(Debug)] +pub(crate) struct Errno(pub(crate) NonZeroI32); +#[allow( + dead_code, + reason = "complete errno table mirrors kernel FUSE reply vocabulary" +)] +impl Errno { + /// Operation not permitted + pub(crate) const EPERM: Errno = errno!(libc::EPERM); + /// No such file or directory + pub(crate) const ENOENT: Errno = errno!(libc::ENOENT); + /// No such process + pub(crate) const ESRCH: Errno = errno!(libc::ESRCH); + /// Interrupted system call + pub(crate) const EINTR: Errno = errno!(libc::EINTR); + /// Input/output error + pub(crate) const EIO: Errno = errno!(libc::EIO); + /// No such device or address + pub(crate) const ENXIO: Errno = errno!(libc::ENXIO); + /// Argument list too long + pub(crate) const E2BIG: Errno = errno!(libc::E2BIG); + /// Exec format error + pub(crate) const ENOEXEC: Errno = errno!(libc::ENOEXEC); + /// Bad file descriptor + pub(crate) const EBADF: Errno = errno!(libc::EBADF); + /// No child processes + pub(crate) const ECHILD: Errno = errno!(libc::ECHILD); + /// Resource temporarily unavailable + pub(crate) const EAGAIN: Errno = errno!(libc::EAGAIN); + /// Cannot allocate memory + pub(crate) const ENOMEM: Errno = errno!(libc::ENOMEM); + /// Permission denied + pub(crate) const EACCES: Errno = errno!(libc::EACCES); + /// Bad address + pub(crate) const EFAULT: Errno = errno!(libc::EFAULT); + /// Block device required + pub(crate) const ENOTBLK: Errno = errno!(libc::ENOTBLK); + /// Device or resource busy + pub(crate) const EBUSY: Errno = errno!(libc::EBUSY); + /// File exists + pub(crate) const EEXIST: Errno = errno!(libc::EEXIST); + /// Invalid cross-device link + pub(crate) const EXDEV: Errno = errno!(libc::EXDEV); + /// No such device + pub(crate) const ENODEV: Errno = errno!(libc::ENODEV); + /// Not a directory + pub(crate) const ENOTDIR: Errno = errno!(libc::ENOTDIR); + /// Is a directory + pub(crate) const EISDIR: Errno = errno!(libc::EISDIR); + /// Invalid argument + pub(crate) const EINVAL: Errno = errno!(libc::EINVAL); + /// Too many open files in system + pub(crate) const ENFILE: Errno = errno!(libc::ENFILE); + /// Too many open files + pub(crate) const EMFILE: Errno = errno!(libc::EMFILE); + /// Inappropriate ioctl for device + pub(crate) const ENOTTY: Errno = errno!(libc::ENOTTY); + /// Text file busy + pub(crate) const ETXTBSY: Errno = errno!(libc::ETXTBSY); + /// File too large + pub(crate) const EFBIG: Errno = errno!(libc::EFBIG); + /// No space left on device + pub(crate) const ENOSPC: Errno = errno!(libc::ENOSPC); + /// Illegal seek + pub(crate) const ESPIPE: Errno = errno!(libc::ESPIPE); + /// Read-only file system + pub(crate) const EROFS: Errno = errno!(libc::EROFS); + /// Too many links + pub(crate) const EMLINK: Errno = errno!(libc::EMLINK); + /// Broken pipe + pub(crate) const EPIPE: Errno = errno!(libc::EPIPE); + /// Numerical argument out of domain + pub(crate) const EDOM: Errno = errno!(libc::EDOM); + /// Numerical result out of range + pub(crate) const ERANGE: Errno = errno!(libc::ERANGE); + /// Resource deadlock avoided + pub(crate) const EDEADLK: Errno = errno!(libc::EDEADLK); + /// File name too long + pub(crate) const ENAMETOOLONG: Errno = errno!(libc::ENAMETOOLONG); + /// No locks available + pub(crate) const ENOLCK: Errno = errno!(libc::ENOLCK); + /// Function not implemented + pub(crate) const ENOSYS: Errno = errno!(libc::ENOSYS); + /// Directory not empty + pub(crate) const ENOTEMPTY: Errno = errno!(libc::ENOTEMPTY); + /// Too many levels of symbolic links + pub(crate) const ELOOP: Errno = errno!(libc::ELOOP); + /// Resource temporarily unavailable + pub(crate) const EWOULDBLOCK: Errno = errno!(libc::EWOULDBLOCK); + /// No message of desired type + pub(crate) const ENOMSG: Errno = errno!(libc::ENOMSG); + /// Identifier removed + pub(crate) const EIDRM: Errno = errno!(libc::EIDRM); + /// Object is remote + pub(crate) const EREMOTE: Errno = errno!(libc::EREMOTE); + /// Link has been severed + pub(crate) const ENOLINK: Errno = errno!(libc::ENOLINK); + /// Protocol error + pub(crate) const EPROTO: Errno = errno!(libc::EPROTO); + /// Multihop attempted + pub(crate) const EMULTIHOP: Errno = errno!(libc::EMULTIHOP); + /// Bad message + pub(crate) const EBADMSG: Errno = errno!(libc::EBADMSG); + /// Value too large for defined data type + pub(crate) const EOVERFLOW: Errno = errno!(libc::EOVERFLOW); + /// Invalid or incomplete multibyte or wide character + pub(crate) const EILSEQ: Errno = errno!(libc::EILSEQ); + /// Too many users + pub(crate) const EUSERS: Errno = errno!(libc::EUSERS); + /// Socket operation on non-socket + pub(crate) const ENOTSOCK: Errno = errno!(libc::ENOTSOCK); + /// Destination address required + pub(crate) const EDESTADDRREQ: Errno = errno!(libc::EDESTADDRREQ); + /// Message too long + pub(crate) const EMSGSIZE: Errno = errno!(libc::EMSGSIZE); + /// Protocol wrong type for socket + pub(crate) const EPROTOTYPE: Errno = errno!(libc::EPROTOTYPE); + /// Protocol not available + pub(crate) const ENOPROTOOPT: Errno = errno!(libc::ENOPROTOOPT); + /// Protocol not supported + pub(crate) const EPROTONOSUPPORT: Errno = errno!(libc::EPROTONOSUPPORT); + /// Socket type not supported + pub(crate) const ESOCKTNOSUPPORT: Errno = errno!(libc::ESOCKTNOSUPPORT); + /// Operation not supported + pub(crate) const EOPNOTSUPP: Errno = errno!(libc::EOPNOTSUPP); + /// Protocol family not supported + pub(crate) const EPFNOSUPPORT: Errno = errno!(libc::EPFNOSUPPORT); + /// Address family not supported by protocol + pub(crate) const EAFNOSUPPORT: Errno = errno!(libc::EAFNOSUPPORT); + /// Address already in use + pub(crate) const EADDRINUSE: Errno = errno!(libc::EADDRINUSE); + /// Cannot assign requested address + pub(crate) const EADDRNOTAVAIL: Errno = errno!(libc::EADDRNOTAVAIL); + /// Network is down + pub(crate) const ENETDOWN: Errno = errno!(libc::ENETDOWN); + /// Network is unreachable + pub(crate) const ENETUNREACH: Errno = errno!(libc::ENETUNREACH); + /// Network dropped connection on reset + pub(crate) const ENETRESET: Errno = errno!(libc::ENETRESET); + /// Software caused connection abort + pub(crate) const ECONNABORTED: Errno = errno!(libc::ECONNABORTED); + /// Connection reset by peer + pub(crate) const ECONNRESET: Errno = errno!(libc::ECONNRESET); + /// No buffer space available + pub(crate) const ENOBUFS: Errno = errno!(libc::ENOBUFS); + /// Transport endpoint is already connected + pub(crate) const EISCONN: Errno = errno!(libc::EISCONN); + /// Transport endpoint is not connected + pub(crate) const ENOTCONN: Errno = errno!(libc::ENOTCONN); + /// Cannot send after transport endpoint shutdown + pub(crate) const ESHUTDOWN: Errno = errno!(libc::ESHUTDOWN); + /// Too many references: cannot splice + pub(crate) const ETOOMANYREFS: Errno = errno!(libc::ETOOMANYREFS); + /// Connection timed out + pub(crate) const ETIMEDOUT: Errno = errno!(libc::ETIMEDOUT); + /// Connection refused + pub(crate) const ECONNREFUSED: Errno = errno!(libc::ECONNREFUSED); + /// Host is down + pub(crate) const EHOSTDOWN: Errno = errno!(libc::EHOSTDOWN); + /// No route to host + pub(crate) const EHOSTUNREACH: Errno = errno!(libc::EHOSTUNREACH); + /// Operation already in progress + pub(crate) const EALREADY: Errno = errno!(libc::EALREADY); + /// Operation now in progress + pub(crate) const EINPROGRESS: Errno = errno!(libc::EINPROGRESS); + /// Stale file handle + pub(crate) const ESTALE: Errno = errno!(libc::ESTALE); + /// Disk quota exceeded + pub(crate) const EDQUOT: Errno = errno!(libc::EDQUOT); + /// Operation cancelled + pub(crate) const ECANCELED: Errno = errno!(libc::ECANCELED); + /// Owner died + pub(crate) const EOWNERDEAD: Errno = errno!(libc::EOWNERDEAD); + /// State not recoverable + pub(crate) const ENOTRECOVERABLE: Errno = errno!(libc::ENOTRECOVERABLE); + /// Operation not supported + pub(crate) const ENOTSUP: Errno = errno!(libc::ENOTSUP); + + /// No data available + pub(crate) const ENODATA: Errno = errno!(libc::ENODATA); + #[doc = no_xattr_doc!()] + pub(crate) const NO_XATTR: Errno = Self::ENODATA; + + pub(crate) fn from_i32(err: i32) -> Errno { + err.try_into().ok().map_or(Errno::EIO, Errno) + } +} +impl From for Errno { + fn from(err: std::io::Error) -> Self { + let errno = err.raw_os_error().unwrap_or(0); + match errno.try_into() { + Ok(i) => Errno(i), + Err(_) => Errno::EIO, + } + } +} +impl From for Errno { + fn from(x: nix::errno::Errno) -> Self { + let err: std::io::Error = x.into(); + err.into() + } +} +impl From for Errno { + fn from(x: std::io::ErrorKind) -> Self { + let err: std::io::Error = x.into(); + err.into() + } +} +impl From for i32 { + fn from(x: Errno) -> Self { + x.0.into() + } +} + +/// A newtype for generation numbers +/// +/// If the file system will be exported over NFS, the (ino, generation) pairs +/// need to be unique over the file system's lifetime (rather than just the +/// mount time). So if the file system reuses an inode after it has been +/// deleted, it must assign a new, previously unused generation number to the +/// inode at the same time. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct Generation(pub(crate) u64); +impl From for u64 { + fn from(fh: Generation) -> Self { + fh.0 + } +} + +#[cfg(test)] +mod test { + use std::io::IoSlice; + use std::ops::{Deref, DerefMut}; + /// If we want to be able to cast bytes to our fuse C struct types we need it + /// to be aligned. This struct helps getting &[u8]s which are 8 byte aligned. + #[cfg(test)] + #[repr(align(8))] + pub(crate) struct AlignedData(pub(crate) T); + impl Deref for AlignedData { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + impl DerefMut for AlignedData { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + pub(crate) fn ioslice_to_vec(s: &[IoSlice<'_>]) -> Vec { + let mut v = Vec::with_capacity(s.iter().map(|x| x.len()).sum()); + for x in s { + v.extend_from_slice(x); + } + v + } +} diff --git a/cli/src/fuser/ll/notify.rs b/crates/agentfs-fuse/src/transport/ll/notify.rs similarity index 53% rename from cli/src/fuser/ll/notify.rs rename to crates/agentfs-fuse/src/transport/ll/notify.rs index 8217c04f..9b7c6503 100644 --- a/cli/src/fuser/ll/notify.rs +++ b/crates/agentfs-fuse/src/transport/ll/notify.rs @@ -1,7 +1,7 @@ -use std::{convert::TryInto, io::IoSlice, mem::size_of, num::TryFromIntError}; - -#[allow(unused)] -use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; +use std::{ + convert::TryInto, ffi::OsStr, io::IoSlice, mem::size_of, num::TryFromIntError, + os::unix::ffi::OsStrExt, +}; use smallvec::{smallvec, SmallVec}; use zerocopy::{Immutable, IntoBytes}; @@ -16,13 +16,8 @@ pub(crate) enum Notification<'a> { /// For notifications with no additional data Bare(NotificationBuf), - /// For notifications that include a buffer of arbitrary data - #[allow(dead_code)] - WithData(NotificationBuf, &'a [u8]), - /// For notifications that include a NUL-terminated name /// (directory entry) - #[allow(unused)] WithName(NotificationBuf, &'a [u8]), } @@ -34,7 +29,6 @@ impl<'a> Notification<'a> { ) -> Result { let datalen = match &self { Notification::Bare(b) => b.len(), - Notification::WithData(b, d) => b.len() + d.len(), Notification::WithName(b, n) => b.len() + n.len() + 1, // +1 because we need to NUL-terminate the name }; let header = abi::fuse_out_header { @@ -45,10 +39,6 @@ impl<'a> Notification<'a> { let mut v: SmallVec<[IoSlice<'_>; 4]> = smallvec![IoSlice::new(header.as_bytes())]; match &self { Notification::Bare(b) => v.push(IoSlice::new(b)), - Notification::WithData(b, d) => { - v.push(IoSlice::new(b)); - v.push(IoSlice::new(d)); - } Notification::WithName(b, n) => { v.push(IoSlice::new(b)); v.push(IoSlice::new(n)); @@ -76,52 +66,13 @@ impl<'a> Notification<'a> { Self::from_struct(&r) } - pub(crate) fn new_store( - ino: u64, - offset: u64, - data: &'a [u8], - ) -> Result { - let r = abi::fuse_notify_store_out { - nodeid: ino, - offset, - size: data.len().try_into()?, - padding: 0, - }; - Ok(Self::from_struct_with_data(&r, data)) - } - - pub(crate) fn new_delete( - parent: u64, - child: u64, - name: &'a OsStr, - ) -> Result { - let r = abi::fuse_notify_delete_out { - parent, - child, - namelen: name.len().try_into()?, - padding: 0, - }; - Ok(Self::from_struct_with_name(&r, name.as_bytes())) - } - - pub(crate) fn new_poll(kh: u64) -> Self { - let r = abi::fuse_notify_poll_wakeup_out { kh }; - Self::from_struct(&r) - } - fn from_struct(data: &T) -> Self { Self::Bare(data.as_bytes().into()) } - #[allow(unused)] fn from_struct_with_name(buf: &T, name: &'a [u8]) -> Self { Self::WithName(buf.as_bytes().into(), name) } - - #[allow(dead_code)] - fn from_struct_with_data(buf: &T, data: &'a [u8]) -> Self { - Self::WithData(buf.as_bytes().into(), data) - } } #[cfg(test)] @@ -161,45 +112,4 @@ mod test { ]; assert_eq!(n, expected); } - - #[test] - fn store() { - let n = Notification::new_store(0x42, 50, &[0xde, 0xad, 0xbe, 0xef]) - .unwrap() - .with_iovec(abi::fuse_notify_code::FUSE_NOTIFY_STORE, ioslice_to_vec) - .unwrap(); - let expected = vec![ - 0x2c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, - 0xbe, 0xef, - ]; - assert_eq!(n, expected); - } - - #[test] - fn delete() { - let n = Notification::new_inval_entry(0x42, OsStr::new("abc")) - .unwrap() - .with_iovec(abi::fuse_notify_code::FUSE_NOTIFY_DELETE, ioslice_to_vec) - .unwrap(); - let expected = vec![ - 0x24, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x61, 0x62, 0x63, 0x00, - ]; - assert_eq!(n, expected); - } - - #[test] - fn poll() { - let n = Notification::new_poll(0x4321) - .with_iovec(abi::fuse_notify_code::FUSE_POLL, ioslice_to_vec) - .unwrap(); - let expected = vec![ - 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - assert_eq!(n, expected); - } } diff --git a/cli/src/fuser/ll/reply.rs b/crates/agentfs-fuse/src/transport/ll/reply.rs similarity index 71% rename from cli/src/fuser/ll/reply.rs rename to crates/agentfs-fuse/src/transport/ll/reply.rs index 022eef51..280e6d0d 100644 --- a/cli/src/fuser/ll/reply.rs +++ b/crates/agentfs-fuse/src/transport/ll/reply.rs @@ -18,7 +18,7 @@ const INLINE_DATA_THRESHOLD: usize = size_of::() * 4; pub(crate) type ResponseBuf = SmallVec<[u8; INLINE_DATA_THRESHOLD]>; #[derive(Debug)] -pub enum Response<'a> { +pub(crate) enum Response<'a> { Error(i32), Data(ResponseBuf), Slice(&'a [u8]), @@ -110,20 +110,14 @@ impl<'a> Response<'a> { atime: 0, mtime: 0, ctime: 0, - #[cfg(target_os = "macos")] - crtime: 0, atimensec: 0, mtimensec: 0, ctimensec: 0, - #[cfg(target_os = "macos")] - crtimensec: 0, mode: 0, nlink: 0, uid: 0, gid: 0, rdev: 0, - #[cfg(target_os = "macos")] - flags: 0, blksize: 0, padding: 0, }, @@ -141,35 +135,20 @@ impl<'a> Response<'a> { Self::from_struct(&r) } - #[cfg(target_os = "macos")] - pub(crate) fn new_xtimes(bkuptime: SystemTime, crtime: SystemTime) -> Self { - let (bkuptime_secs, bkuptime_nanos) = time_from_system_time(&bkuptime); - let (crtime_secs, crtime_nanos) = time_from_system_time(&crtime); - let r = abi::fuse_getxtimes_out { - bkuptime: bkuptime_secs as u64, - crtime: crtime_secs as u64, - bkuptimensec: bkuptime_nanos, - crtimensec: crtime_nanos, - }; - Self::from_struct(&r) - } - // TODO: Could flags be more strongly typed? - pub(crate) fn new_open(fh: FileHandle, flags: u32, backing_id: u32) -> Self { - #[cfg(not(feature = "abi-7-40"))] - let _ = backing_id; - + pub(crate) fn new_open(fh: FileHandle, flags: u32) -> Self { let r = abi::fuse_open_out { fh: fh.into(), open_flags: flags, - #[cfg(not(feature = "abi-7-40"))] padding: 0, - #[cfg(feature = "abi-7-40")] - backing_id, }; Self::from_struct(&r) } + #[allow( + dead_code, + reason = "success builder retained for unsupported lock reply type" + )] pub(crate) fn new_lock(lock: &Lock) -> Self { let r = abi::fuse_lk_out { lk: abi::fuse_file_lock { @@ -182,6 +161,10 @@ impl<'a> Response<'a> { Self::from_struct(&r) } + #[allow( + dead_code, + reason = "success builder retained for unsupported bmap reply type" + )] pub(crate) fn new_bmap(block: u64) -> Self { let r = abi::fuse_bmap_out { block }; Self::from_struct(&r) @@ -231,11 +214,7 @@ impl<'a> Response<'a> { generation: Generation, fh: FileHandle, flags: u32, - backing_id: u32, ) -> Self { - #[cfg(not(feature = "abi-7-40"))] - let _ = backing_id; - let r = abi::fuse_create_out( abi::fuse_entry_out { nodeid: attr.attr.ino, @@ -249,16 +228,17 @@ impl<'a> Response<'a> { abi::fuse_open_out { fh: fh.into(), open_flags: flags, - #[cfg(not(feature = "abi-7-40"))] padding: 0, - #[cfg(feature = "abi-7-40")] - backing_id, }, ); Self::from_struct(&r) } // TODO: Are you allowed to send data while result != 0? + #[allow( + dead_code, + reason = "success builder retained for unsupported ioctl reply type" + )] pub(crate) fn new_ioctl(result: i32, data: &[IoSlice<'_>]) -> Self { let r = abi::fuse_ioctl_out { result, @@ -276,6 +256,10 @@ impl<'a> Response<'a> { Self::Data(v) } + #[allow( + dead_code, + reason = "success builder retained for unsupported poll reply type" + )] pub(crate) fn new_poll(revents: u32) -> Self { let r = abi::fuse_poll_out { revents, @@ -289,11 +273,19 @@ impl<'a> Response<'a> { Self::Data(list.buf) } + #[allow( + dead_code, + reason = "success builder retained for unsupported xattr reply type" + )] pub(crate) fn new_xattr_size(size: u32) -> Self { let r = abi::fuse_getxattr_out { size, padding: 0 }; Self::from_struct(&r) } + #[allow( + dead_code, + reason = "success builder retained for unsupported lseek reply type" + )] pub(crate) fn new_lseek(offset: i64) -> Self { let r = abi::fuse_lseek_out { offset }; Self::from_struct(&r) @@ -314,8 +306,7 @@ pub(crate) fn time_from_system_time(system_time: &SystemTime) -> (i64, u32) { ), } } -// Some platforms like Linux x86_64 have mode_t = u32, and lint warns of a trivial_numeric_casts. -// But others like macOS x86_64 have mode_t = u16, requiring a typecast. So, just silence lint. +// libc mode constants can vary by target width; keep the cast explicit at the ABI boundary. #[allow(trivial_numeric_casts)] #[allow(clippy::unnecessary_cast)] /// Returns the mode for a given file kind and permission @@ -336,8 +327,6 @@ pub(crate) fn fuse_attr_from_attr(attr: &super::super::FileAttr) -> abi::fuse_at let (atime_secs, atime_nanos) = time_from_system_time(&attr.atime); let (mtime_secs, mtime_nanos) = time_from_system_time(&attr.mtime); let (ctime_secs, ctime_nanos) = time_from_system_time(&attr.ctime); - #[cfg(target_os = "macos")] - let (crtime_secs, crtime_nanos) = time_from_system_time(&attr.crtime); abi::fuse_attr { ino: attr.ino, @@ -346,20 +335,14 @@ pub(crate) fn fuse_attr_from_attr(attr: &super::super::FileAttr) -> abi::fuse_at atime: atime_secs, mtime: mtime_secs, ctime: ctime_secs, - #[cfg(target_os = "macos")] - crtime: crtime_secs as u64, atimensec: atime_nanos, mtimensec: mtime_nanos, ctimensec: ctime_nanos, - #[cfg(target_os = "macos")] - crtimensec: crtime_nanos, mode: mode_from_kind_and_perm(attr.kind, attr.perm), nlink: attr.nlink, uid: attr.uid, gid: attr.gid, rdev: attr.rdev, - #[cfg(target_os = "macos")] - flags: attr.flags, blksize: attr.blksize, padding: 0, } @@ -367,7 +350,7 @@ pub(crate) fn fuse_attr_from_attr(attr: &super::super::FileAttr) -> abi::fuse_at // TODO: Add methods for creating this without making a `FileAttr` first. #[derive(Debug, Clone, Copy)] -pub struct Attr { +pub(crate) struct Attr { pub(crate) attr: abi::fuse_attr, } impl From<&super::super::FileAttr> for Attr { @@ -418,7 +401,7 @@ impl EntListBuf { } #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] -pub struct DirEntOffset(pub i64); +pub(crate) struct DirEntOffset(pub(crate) i64); impl From for i64 { fn from(x: DirEntOffset) -> Self { x.0 @@ -426,7 +409,7 @@ impl From for i64 { } #[derive(Debug)] -pub struct DirEntry> { +pub(crate) struct DirEntry> { ino: INodeNo, offset: DirEntOffset, kind: FileType, @@ -434,7 +417,7 @@ pub struct DirEntry> { } impl> DirEntry { - pub fn new(ino: INodeNo, offset: DirEntOffset, kind: FileType, name: T) -> DirEntry { + pub(crate) fn new(ino: INodeNo, offset: DirEntOffset, kind: FileType, name: T) -> DirEntry { DirEntry:: { ino, offset, @@ -446,7 +429,7 @@ impl> DirEntry { /// Data buffer used to respond to [`Readdir`] requests. #[derive(Debug)] -pub struct DirEntList(EntListBuf); +pub(crate) struct DirEntList(EntListBuf); impl From for Response<'_> { fn from(l: DirEntList) -> Self { assert!(l.0.buf.len() <= l.0.max_size); @@ -462,7 +445,7 @@ impl DirEntList { /// A transparent offset value can be provided for each entry. The kernel uses these /// value to request the next entries in further readdir calls #[must_use] - pub fn push>(&mut self, ent: &DirEntry) -> bool { + pub(crate) fn push>(&mut self, ent: &DirEntry) -> bool { let name = ent.name.as_ref().as_os_str().as_bytes(); let header = abi::fuse_dirent { ino: ent.ino.into(), @@ -475,7 +458,7 @@ impl DirEntList { } #[derive(Debug)] -pub struct DirEntryPlus> { +pub(crate) struct DirEntryPlus> { #[allow(unused)] // We use `attr.ino` instead ino: INodeNo, generation: Generation, @@ -487,7 +470,7 @@ pub struct DirEntryPlus> { } impl> DirEntryPlus { - pub fn new( + pub(crate) fn new( ino: INodeNo, generation: Generation, offset: DirEntOffset, @@ -510,7 +493,7 @@ impl> DirEntryPlus { /// Data buffer used to respond to [`ReaddirPlus`] requests. #[derive(Debug)] -pub struct DirEntPlusList(EntListBuf); +pub(crate) struct DirEntPlusList(EntListBuf); impl From for Response<'_> { fn from(l: DirEntPlusList) -> Self { assert!(l.0.buf.len() <= l.0.max_size); @@ -526,7 +509,7 @@ impl DirEntPlusList { /// A transparent offset value can be provided for each entry. The kernel uses these /// value to request the next entries in further readdir calls #[must_use] - pub fn push>(&mut self, x: &DirEntryPlus) -> bool { + pub(crate) fn push>(&mut self, x: &DirEntryPlus) -> bool { let name = x.name.as_ref().as_os_str().as_bytes(); let header = abi::fuse_direntplus { entry_out: abi::fuse_entry_out { @@ -555,7 +538,7 @@ mod test { use super::super::test::ioslice_to_vec; use super::*; - use crate::fuser::FileAttr; + use crate::transport::FileAttr; #[test] fn reply_empty() { @@ -595,34 +578,18 @@ mod test { #[test] fn reply_entry() { - let mut expected = if cfg!(target_os = "macos") { - vec![ - 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, - 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, - 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, - ] - } else { - vec![ - 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, - 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, - ] - }; + let mut expected = vec![ + 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, + 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, + 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, + 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + ]; expected.extend(vec![0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); expected[0] = (expected.len()) as u8; @@ -655,31 +622,16 @@ mod test { #[test] fn reply_attr() { - let mut expected = if cfg!(target_os = "macos") { - vec![ - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, - 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, - 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, - 0x00, 0x00, - ] - } else { - vec![ - 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, - 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, - ] - }; + let mut expected = vec![ + 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, + 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, + 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, + 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + ]; expected.extend_from_slice(&[0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); expected[0] = expected.len() as u8; @@ -710,22 +662,6 @@ mod test { ); } - #[test] - #[cfg(target_os = "macos")] - fn reply_xtimes() { - let expected = vec![ - 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, - ]; - let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); - let r = Response::new_xtimes(time, time); - assert_eq!( - r.with_iovec(RequestId(0xdeadbeef), ioslice_to_vec), - expected - ); - } - #[test] fn reply_open() { let expected = vec![ @@ -733,7 +669,7 @@ mod test { 0x00, 0x00, 0x22, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; - let r = Response::new_open(FileHandle(0x1122), 0x33, 0); + let r = Response::new_open(FileHandle(0x1122), 0x33); assert_eq!( r.with_iovec(RequestId(0xdeadbeef), ioslice_to_vec), expected @@ -773,36 +709,19 @@ mod test { #[test] fn reply_create() { - let mut expected = if cfg!(target_os = "macos") { - vec![ - 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, - 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, - 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0xbb, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ] - } else { - vec![ - 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, - 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ] - }; + let mut expected = vec![ + 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, + 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, + 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, + 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; let insert_at = expected.len() - 16; expected.splice( @@ -837,7 +756,6 @@ mod test { Generation(0xaa), FileHandle(0xbb), 0xcc, - 0, ); assert_eq!( r.with_iovec(RequestId(0xdeadbeef), ioslice_to_vec), diff --git a/cli/src/fuser/ll/request.rs b/crates/agentfs-fuse/src/transport/ll/request.rs similarity index 76% rename from cli/src/fuser/ll/request.rs rename to crates/agentfs-fuse/src/transport/ll/request.rs index 519d10b5..1b49d270 100644 --- a/cli/src/fuser/ll/request.rs +++ b/crates/agentfs-fuse/src/transport/ll/request.rs @@ -13,7 +13,7 @@ use super::argument::ArgumentIterator; /// Error that may occur while reading and parsing a request from the kernel driver. #[derive(Debug)] -pub enum RequestError { +pub(crate) enum RequestError { /// Not enough data for parsing header (short read). ShortReadHeader(usize), /// Kernel requested an unknown operation. @@ -35,7 +35,7 @@ pub enum RequestError { /// important to handle this for any requests that may block indefinitely, like /// [`SetLkW`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct RequestId(pub u64); +pub(crate) struct RequestId(pub(crate) u64); impl From for u64 { fn from(fh: RequestId) -> Self { fh.0 @@ -66,7 +66,7 @@ impl From for u64 { /// not all [`u64`]s are valid [`INodeNo`]s, but the reverse is true. So to produce /// a [`INodeNo`] from a [`u64`] we must be explicit. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct INodeNo(pub u64); +pub(crate) struct INodeNo(pub(crate) u64); impl From for u64 { fn from(fh: INodeNo) -> Self { fh.0 @@ -109,7 +109,7 @@ impl From for u64 { /// not all [`u64`]s are valid `FileHandles`, but the reverse is true. So to produce /// a `FileHandle` from a [`u64`] we must be explicit. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct FileHandle(pub u64); +pub(crate) struct FileHandle(pub(crate) u64); impl From for u64 { fn from(fh: FileHandle) -> Self { @@ -127,7 +127,7 @@ impl From for u64 { /// because all `LockOwners` are valid [`u64`]s, but not vice-versa. So to produce /// a [`LockOwner`] from a [`u64`] we must be explicit. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct LockOwner(pub u64); +pub(crate) struct LockOwner(pub(crate) u64); impl From for u64 { fn from(fh: LockOwner) -> Self { @@ -136,13 +136,13 @@ impl From for u64 { } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Lock { +pub(crate) struct Lock { // Unfortunately this can't be a std::ops::Range because Range is not Copy: // https://github.com/rust-lang/rfcs/issues/2848 - pub range: (u64, u64), + pub(crate) range: (u64, u64), // TODO: Make typ an enum - pub typ: i32, - pub pid: u32, + pub(crate) typ: i32, + pub(crate) pid: u32, } impl Lock { fn from_abi(x: &abi::fuse_file_lock) -> Lock { @@ -156,12 +156,12 @@ impl Lock { /// A newtype for ABI version #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Version(pub u32, pub u32); +pub(crate) struct Version(pub(crate) u32, pub(crate) u32); impl Version { - pub fn major(&self) -> u32 { + pub(crate) fn major(&self) -> u32 { self.0 } - pub fn minor(&self) -> u32 { + pub(crate) fn minor(&self) -> u32 { self.1 } } @@ -173,13 +173,13 @@ impl Display for Version { /// Represents a filename in a directory #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -pub struct FilenameInDir<'a> { +pub(crate) struct FilenameInDir<'a> { /// The Inode number of the directory - pub dir: INodeNo, + pub(crate) dir: INodeNo, /// Name of the file. This refers to a name directly in this directory, rather than any /// subdirectory so is guaranteed not to contain '\0' or '/'. It may be literally "." or ".." /// however. - pub name: &'a Path, + pub(crate) name: &'a Path, } impl fmt::Display for RequestError { @@ -200,7 +200,7 @@ impl fmt::Display for RequestError { } impl error::Error for RequestError {} -pub trait Request: Sized { +pub(crate) trait Request: Sized { /// Returns the unique identifier of this request. /// /// The FUSE kernel driver assigns a unique id to every concurrent request. This allows to @@ -268,10 +268,8 @@ mod op { abi::consts::*, abi::*, FileHandle, INodeNo, Lock, LockOwner, Operation, RequestId, }; use std::{ - convert::TryInto, ffi::OsStr, fmt::Display, - num::NonZeroU32, path::Path, time::{Duration, SystemTime}, }; @@ -283,13 +281,13 @@ mod op { /// about `INode` lifecycle and the relationship between [`Lookup`] and [`Forget`] in the /// documentation for [`INodeNo`]. #[derive(Debug)] - pub struct Lookup<'a> { + pub(crate) struct Lookup<'a> { header: &'a fuse_in_header, name: &'a OsStr, } impl_request!(Lookup<'_>); impl<'a> Lookup<'a> { - pub fn name(&self) -> &'a Path { + pub(crate) fn name(&self) -> &'a Path { self.name.as_ref() } } @@ -306,21 +304,21 @@ mod op { /// On unmount it is not guaranteed, that all referenced inodes will receive a forget /// message. #[derive(Debug)] - pub struct Forget<'a> { + pub(crate) struct Forget<'a> { header: &'a fuse_in_header, arg: &'a fuse_forget_in, } impl_request!(Forget<'_>); impl Forget<'_> { /// The number of lookups previously performed on this inode - pub fn nlookup(&self) -> u64 { + pub(crate) fn nlookup(&self) -> u64 { self.arg.nlookup } } /// Get file attributes. #[derive(Debug)] - pub struct GetAttr<'a> { + pub(crate) struct GetAttr<'a> { header: &'a fuse_in_header, arg: &'a fuse_getattr_in, @@ -328,7 +326,7 @@ mod op { impl_request!(GetAttr<'_>); impl GetAttr<'_> { - pub fn file_handle(&self) -> Option { + pub(crate) fn file_handle(&self) -> Option { if self.arg.getattr_flags & FUSE_GETATTR_FH != 0 { Some(FileHandle(self.arg.fh)) } else { @@ -339,37 +337,37 @@ mod op { /// Set file attributes. #[derive(Debug)] - pub struct SetAttr<'a> { + pub(crate) struct SetAttr<'a> { header: &'a fuse_in_header, arg: &'a fuse_setattr_in, } impl_request!(SetAttr<'_>); impl SetAttr<'_> { - pub fn mode(&self) -> Option { + pub(crate) fn mode(&self) -> Option { match self.arg.valid & FATTR_MODE { 0 => None, _ => Some(self.arg.mode), } } - pub fn uid(&self) -> Option { + pub(crate) fn uid(&self) -> Option { match self.arg.valid & FATTR_UID { 0 => None, _ => Some(self.arg.uid), } } - pub fn gid(&self) -> Option { + pub(crate) fn gid(&self) -> Option { match self.arg.valid & FATTR_GID { 0 => None, _ => Some(self.arg.gid), } } - pub fn size(&self) -> Option { + pub(crate) fn size(&self) -> Option { match self.arg.valid & FATTR_SIZE { 0 => None, _ => Some(self.arg.size), } } - pub fn atime(&self) -> Option { + pub(crate) fn atime(&self) -> Option { match self.arg.valid & FATTR_ATIME { 0 => None, _ => Some(if self.arg.atime_now() { @@ -382,7 +380,7 @@ mod op { }), } } - pub fn mtime(&self) -> Option { + pub(crate) fn mtime(&self) -> Option { match self.arg.valid & FATTR_MTIME { 0 => None, _ => Some(if self.arg.mtime_now() { @@ -395,73 +393,22 @@ mod op { }), } } - pub fn ctime(&self) -> Option { - #[cfg(feature = "abi-7-23")] + pub(crate) fn ctime(&self) -> Option { match self.arg.valid & FATTR_CTIME { 0 => None, _ => Some(system_time_from_time(self.arg.ctime, self.arg.ctimensec)), } - #[cfg(not(feature = "abi-7-23"))] - None } /// The value set by the [`Open`] method. See [`FileHandle`]. /// /// This will only be set if the user passed a file-descriptor to set the /// attributes - i.e. they used [`libc::fchmod`] rather than [`libc::chmod`]. - pub fn file_handle(&self) -> Option { + pub(crate) fn file_handle(&self) -> Option { match self.arg.valid & FATTR_FH { 0 => None, _ => Some(FileHandle(self.arg.fh)), } } - pub fn crtime(&self) -> Option { - #[cfg(target_os = "macos")] - match self.arg.valid & FATTR_CRTIME { - 0 => None, - // During certain operation, macOS use some helper that send request to the mountpoint with `crtime` set to 0xffffffff83da4f80. - // That value correspond to `-2_082_844_800u64` which is the difference between the date 1904-01-01 and 1970-01-01 because macOS epoch start at 1904 and not 1970. - // https://github.com/macfuse/macfuse/issues/1042 - _ if self.arg.crtime == 0xffffffff83da4f80 => None, - _ => Some( - SystemTime::UNIX_EPOCH + Duration::new(self.arg.crtime, self.arg.crtimensec), - ), - } - #[cfg(not(target_os = "macos"))] - None - } - pub fn chgtime(&self) -> Option { - #[cfg(target_os = "macos")] - match self.arg.valid & FATTR_CHGTIME { - 0 => None, - _ => Some( - SystemTime::UNIX_EPOCH + Duration::new(self.arg.chgtime, self.arg.chgtimensec), - ), - } - #[cfg(not(target_os = "macos"))] - None - } - pub fn bkuptime(&self) -> Option { - #[cfg(target_os = "macos")] - match self.arg.valid & FATTR_BKUPTIME { - 0 => None, - _ => Some( - SystemTime::UNIX_EPOCH - + Duration::new(self.arg.bkuptime, self.arg.bkuptimensec), - ), - } - #[cfg(not(target_os = "macos"))] - None - } - pub fn flags(&self) -> Option { - #[cfg(target_os = "macos")] - match self.arg.valid & FATTR_FLAGS { - 0 => None, - _ => Some(self.arg.flags), - } - #[cfg(not(target_os = "macos"))] - None - } - // TODO: Why does *set*attr want to have an attr response? } impl Display for SetAttr<'_> { @@ -469,8 +416,7 @@ mod op { write!( f, "SETATTR mode: {:?}, uid: {:?}, gid: {:?}, size: {:?}, atime: {:?}, \ - mtime: {:?}, ctime: {:?}, file_handle: {:?}, crtime: {:?}, chgtime: {:?}, \ - bkuptime: {:?}, flags: {:?}", + mtime: {:?}, ctime: {:?}, file_handle: {:?}", self.mode(), self.uid(), self.gid(), @@ -478,35 +424,24 @@ mod op { self.atime(), self.mtime(), self.ctime(), - self.file_handle(), - self.crtime(), - self.chgtime(), - self.bkuptime(), - self.flags() + self.file_handle() ) } } - /// Read symbolic link. - #[derive(Debug)] - pub struct ReadLink<'a> { - header: &'a fuse_in_header, - } - impl_request!(ReadLink<'_>); - /// Create a symbolic link. #[derive(Debug)] - pub struct SymLink<'a> { + pub(crate) struct SymLink<'a> { header: &'a fuse_in_header, target: &'a Path, link_name: &'a Path, } impl_request!(SymLink<'_>); impl<'a> SymLink<'a> { - pub fn target(&self) -> &'a Path { + pub(crate) fn target(&self) -> &'a Path { self.target } - pub fn link_name(&self) -> &'a Path { + pub(crate) fn link_name(&self) -> &'a Path { self.link_name } } @@ -514,76 +449,76 @@ mod op { /// Create file node. /// Create a regular file, character device, block device, fifo or socket node. #[derive(Debug)] - pub struct MkNod<'a> { + pub(crate) struct MkNod<'a> { header: &'a fuse_in_header, arg: &'a fuse_mknod_in, name: &'a Path, } impl_request!(MkNod<'_>); impl<'a> MkNod<'a> { - pub fn name(&self) -> &'a Path { + pub(crate) fn name(&self) -> &'a Path { self.name } - pub fn mode(&self) -> u32 { + pub(crate) fn mode(&self) -> u32 { self.arg.mode } - pub fn umask(&self) -> u32 { + pub(crate) fn umask(&self) -> u32 { self.arg.umask } - pub fn rdev(&self) -> u32 { + pub(crate) fn rdev(&self) -> u32 { self.arg.rdev } } /// Create a directory. #[derive(Debug)] - pub struct MkDir<'a> { + pub(crate) struct MkDir<'a> { header: &'a fuse_in_header, arg: &'a fuse_mkdir_in, name: &'a Path, } impl_request!(MkDir<'_>); impl<'a> MkDir<'a> { - pub fn name(&self) -> &'a Path { + pub(crate) fn name(&self) -> &'a Path { self.name } - pub fn mode(&self) -> u32 { + pub(crate) fn mode(&self) -> u32 { self.arg.mode } - pub fn umask(&self) -> u32 { + pub(crate) fn umask(&self) -> u32 { self.arg.umask } } /// Remove a file. #[derive(Debug)] - pub struct Unlink<'a> { + pub(crate) struct Unlink<'a> { header: &'a fuse_in_header, name: &'a Path, } impl_request!(Unlink<'_>); impl<'a> Unlink<'a> { - pub fn name(&self) -> &'a Path { + pub(crate) fn name(&self) -> &'a Path { self.name } } /// Remove a directory. #[derive(Debug)] - pub struct RmDir<'a> { + pub(crate) struct RmDir<'a> { header: &'a fuse_in_header, - pub name: &'a Path, + pub(crate) name: &'a Path, } impl_request!(RmDir<'_>); impl<'a> RmDir<'a> { - pub fn name(&self) -> &'a Path { + pub(crate) fn name(&self) -> &'a Path { self.name } } /// Rename a file. #[derive(Debug)] - pub struct Rename<'a> { + pub(crate) struct Rename<'a> { header: &'a fuse_in_header, arg: &'a fuse_rename_in, name: &'a Path, @@ -591,13 +526,13 @@ mod op { } impl_request!(Rename<'_>); impl<'a> Rename<'a> { - pub fn src(&self) -> FilenameInDir<'a> { + pub(crate) fn src(&self) -> FilenameInDir<'a> { FilenameInDir::<'a> { dir: self.nodeid(), name: self.name, } } - pub fn dest(&self) -> FilenameInDir<'a> { + pub(crate) fn dest(&self) -> FilenameInDir<'a> { FilenameInDir::<'a> { dir: INodeNo(self.arg.newdir), name: self.newname, @@ -607,7 +542,7 @@ mod op { /// Create a hard link. #[derive(Debug)] - pub struct Link<'a> { + pub(crate) struct Link<'a> { header: &'a fuse_in_header, arg: &'a fuse_link_in, name: &'a Path, @@ -616,10 +551,10 @@ mod op { impl<'a> Link<'a> { /// This is the inode no of the file to be linked. The inode number in /// the fuse header is of the directory that it will be linked into. - pub fn inode_no(&self) -> INodeNo { + pub(crate) fn inode_no(&self) -> INodeNo { INodeNo(self.arg.oldnodeid) } - pub fn dest(&self) -> FilenameInDir<'a> { + pub(crate) fn dest(&self) -> FilenameInDir<'a> { FilenameInDir::<'a> { dir: self.nodeid(), name: self.name, @@ -637,13 +572,13 @@ mod op { /// filesystem may set, to change the way the file is opened. See `fuse_file_info` /// structure in <`fuse_common.h`> for more details. #[derive(Debug)] - pub struct Open<'a> { + pub(crate) struct Open<'a> { header: &'a fuse_in_header, arg: &'a fuse_open_in, } impl_request!(Open<'_>); impl Open<'_> { - pub fn flags(&self) -> i32 { + pub(crate) fn flags(&self) -> i32 { self.arg.flags } } @@ -656,24 +591,24 @@ mod op { /// return value of the read system call will reflect the return value of this /// operation. #[derive(Debug)] - pub struct Read<'a> { + pub(crate) struct Read<'a> { header: &'a fuse_in_header, arg: &'a fuse_read_in, } impl_request!(Read<'_>); impl Read<'_> { /// The value set by the [Open] method. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } - pub fn offset(&self) -> i64 { + pub(crate) fn offset(&self) -> i64 { self.arg.offset } - pub fn size(&self) -> u32 { + pub(crate) fn size(&self) -> u32 { self.arg.size } /// Only supported with ABI >= 7.9 - pub fn lock_owner(&self) -> Option { + pub(crate) fn lock_owner(&self) -> Option { if self.arg.read_flags & FUSE_READ_LOCKOWNER != 0 { Some(LockOwner(self.arg.lock_owner)) } else { @@ -681,7 +616,7 @@ mod op { } } /// The file flags, such as `O_SYNC`. Only supported with ABI >= 7.9 - pub fn flags(&self) -> i32 { + pub(crate) fn flags(&self) -> i32 { self.arg.flags } } @@ -693,7 +628,7 @@ mod op { /// which case the return value of the write system call will reflect the return /// value of this operation. #[derive(Debug)] - pub struct Write<'a> { + pub(crate) struct Write<'a> { header: &'a fuse_in_header, arg: &'a fuse_write_in, data: &'a [u8], @@ -701,13 +636,13 @@ mod op { impl_request!(Write<'_>); impl<'a> Write<'a> { /// The value set by the [Open] method. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } - pub fn offset(&self) -> i64 { + pub(crate) fn offset(&self) -> i64 { self.arg.offset } - pub fn data(&self) -> &'a [u8] { + pub(crate) fn data(&self) -> &'a [u8] { self.data } /// Will contain `FUSE_WRITE_CACHE`, if this write is from the page cache. If set, @@ -715,11 +650,11 @@ mod op { /// is disabled /// /// TODO: `WriteFlags` type or remove this - pub fn write_flags(&self) -> u32 { + pub(crate) fn write_flags(&self) -> u32 { self.arg.write_flags } /// `lock_owner`: only supported with ABI >= 7.9 - pub fn lock_owner(&self) -> Option { + pub(crate) fn lock_owner(&self) -> Option { if self.arg.write_flags & FUSE_WRITE_LOCKOWNER != 0 { Some(LockOwner(self.arg.lock_owner)) } else { @@ -728,18 +663,11 @@ mod op { } /// flags: these are the file flags, such as `O_SYNC`. Only supported with ABI >= 7.9 /// TODO: Make a Flags type specifying valid values - pub fn flags(&self) -> i32 { + pub(crate) fn flags(&self) -> i32 { self.arg.flags } } - /// Get file system statistics. - #[derive(Debug)] - pub struct StatFs<'a> { - header: &'a fuse_in_header, - } - impl_request!(StatFs<'_>); - /// Release an open file. /// /// Release is called when there are no more references to an open file: all file @@ -748,25 +676,25 @@ mod op { /// error, but error values are not returned to `close()` or `munmap()` which /// triggered the release. #[derive(Debug)] - pub struct Release<'a> { + pub(crate) struct Release<'a> { header: &'a fuse_in_header, arg: &'a fuse_release_in, } impl_request!(Release<'_>); impl Release<'_> { - pub fn flush(&self) -> bool { + pub(crate) fn flush(&self) -> bool { self.arg.release_flags & FUSE_RELEASE_FLUSH != 0 } /// The value set by the [`Open`] method. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } /// The same flags as for open. /// TODO: Document what flags are valid, or remove this - pub fn flags(&self) -> i32 { + pub(crate) fn flags(&self) -> i32 { self.arg.flags } - pub fn lock_owner(&self) -> Option { + pub(crate) fn lock_owner(&self) -> Option { if self.arg.release_flags & FUSE_RELEASE_FLOCK_UNLOCK != 0 { Some(LockOwner(self.arg.lock_owner)) } else { @@ -777,25 +705,25 @@ mod op { /// Synchronize file contents. #[derive(Debug)] - pub struct FSync<'a> { + pub(crate) struct FSync<'a> { header: &'a fuse_in_header, arg: &'a fuse_fsync_in, } impl_request!(FSync<'a>); impl FSync<'_> { /// The value set by the [`Open`] method. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } /// If set only the user data should be flushed, not the meta data. - pub fn fdatasync(&self) -> bool { + pub(crate) fn fdatasync(&self) -> bool { self.arg.fsync_flags & consts::FUSE_FSYNC_FDATASYNC != 0 } } /// Set an extended attribute. #[derive(Debug)] - pub struct SetXAttr<'a> { + pub(crate) struct SetXAttr<'a> { header: &'a fuse_in_header, arg: &'a fuse_setxattr_in, name: &'a OsStr, @@ -803,22 +731,17 @@ mod op { } impl_request!(SetXAttr<'a>); impl<'a> SetXAttr<'a> { - pub fn name(&self) -> &'a OsStr { + pub(crate) fn name(&self) -> &'a OsStr { self.name } - pub fn value(&self) -> &'a [u8] { + pub(crate) fn value(&self) -> &'a [u8] { self.value } // TODO: Document what are valid flags - pub fn flags(&self) -> i32 { + pub(crate) fn flags(&self) -> i32 { self.arg.flags } - /// This will always be 0 except on `MacOS`. It's recommended that - /// implementations return EINVAL if this is not 0. - pub fn position(&self) -> u32 { - #[cfg(target_os = "macos")] - return self.arg.position; - #[cfg(not(target_os = "macos"))] + pub(crate) fn position(&self) -> u32 { 0 } } @@ -828,47 +751,19 @@ mod op { /// If the requested `XAttr` doesn't exist, return [`Err(Errno::NO_XATTR)`] which will /// map to the right platform-specific error code. #[derive(Debug)] - pub struct GetXAttr<'a> { + pub(crate) struct GetXAttr<'a> { header: &'a fuse_in_header, arg: &'a fuse_getxattr_in, name: &'a OsStr, } impl_request!(GetXAttr<'a>); - /// Type for [`GetXAttrSizeEnum::GetSize`]. - /// - /// Represents a request from the user to get the size of the data stored in the `XAttr`. - #[derive(Debug)] - pub struct GetXAttrSize(); - - #[derive(Debug)] - /// Return type for [`GetXAttr::size`]. - pub enum GetXAttrSizeEnum { - /// User is requesting the size of the data stored in the `XAttr` - GetSize(GetXAttrSize), - /// User is requesting the data stored in the `XAttr`. If the data will fit - /// in this number of bytes it should be returned, otherwise return [`Err(Errno::ERANGE)`]. - #[allow(dead_code)] - Size(NonZeroU32), - } impl<'a> GetXAttr<'a> { /// Name of the `XAttr` - pub fn name(&self) -> &'a OsStr { + pub(crate) fn name(&self) -> &'a OsStr { self.name } - /// See [`GetXAttrSizeEnum`]. - /// - /// You only need to check this value as an optimisation where there's a - /// cost difference between checking the size of the data stored in an `XAttr` - /// and actually providing the data. Otherwise just call [`reply()`] with the - /// data and it will do the right thing. - pub fn size(&self) -> GetXAttrSizeEnum { - let s: Result = self.arg.size.try_into(); - match s { - Ok(s) => GetXAttrSizeEnum::Size(s), - Err(_) => GetXAttrSizeEnum::GetSize(GetXAttrSize()), - } - } + /// The size of the buffer the user has allocated to store the `XAttr` value. pub(crate) fn size_u32(&self) -> u32 { self.arg.size @@ -877,7 +772,7 @@ mod op { /// List extended attribute names. #[derive(Debug)] - pub struct ListXAttr<'a> { + pub(crate) struct ListXAttr<'a> { header: &'a fuse_in_header, arg: &'a fuse_getxattr_in, } @@ -888,7 +783,7 @@ mod op { /// required to fit the whole list. /// /// You don't need to worry about this except as an optimisation. - pub fn size(&self) -> u32 { + pub(crate) fn size(&self) -> u32 { self.arg.size } } @@ -898,14 +793,14 @@ mod op { /// Return [`Err(Errno::NO_XATTR)`] if the xattr doesn't exist /// Return [`Err(Errno::ENOTSUP)`] if this filesystem doesn't support `XAttrs` #[derive(Debug)] - pub struct RemoveXAttr<'a> { + pub(crate) struct RemoveXAttr<'a> { header: &'a fuse_in_header, name: &'a OsStr, } impl_request!(RemoveXAttr<'a>); impl<'a> RemoveXAttr<'a> { /// Name of the `XAttr` to remove - pub fn name(&self) -> &'a OsStr { + pub(crate) fn name(&self) -> &'a OsStr { self.name } } @@ -922,74 +817,57 @@ mod op { /// filesystem wants to return write errors. If the filesystem supports file locking /// operations (`setlk`, `getlk`) it should remove all locks belonging to `lock_owner`. #[derive(Debug)] - pub struct Flush<'a> { + pub(crate) struct Flush<'a> { header: &'a fuse_in_header, arg: &'a fuse_flush_in, } impl_request!(Flush<'a>); impl Flush<'_> { /// The value set by the open method - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } - pub fn lock_owner(&self) -> LockOwner { + pub(crate) fn lock_owner(&self) -> LockOwner { LockOwner(self.arg.lock_owner) } } #[derive(Debug)] - pub struct Init<'a> { + pub(crate) struct Init<'a> { header: &'a fuse_in_header, arg: &'a fuse_init_in, } impl_request!(Init<'a>); impl<'a> Init<'a> { - pub fn capabilities(&self) -> u64 { - #[cfg(feature = "abi-7-36")] + pub(crate) fn capabilities(&self) -> u64 { if self.arg.flags & (FUSE_INIT_EXT as u32) != 0 { return u64::from(self.arg.flags) | (u64::from(self.arg.flags2) << 32); } u64::from(self.arg.flags) } - pub fn max_readahead(&self) -> u32 { + pub(crate) fn max_readahead(&self) -> u32 { self.arg.max_readahead } - pub fn version(&self) -> super::Version { + pub(crate) fn version(&self) -> super::Version { super::Version(self.arg.major, self.arg.minor) } - pub fn reply(&self, config: &super::super::super::KernelConfig) -> Response<'a> { + pub(crate) fn reply(&self, config: &super::super::super::KernelConfig) -> Response<'a> { let flags = self.capabilities() & config.requested; // use requested features and reported as capable let init = fuse_init_out { major: FUSE_KERNEL_VERSION, minor: FUSE_KERNEL_MINOR_VERSION, max_readahead: config.max_readahead, - #[cfg(not(feature = "abi-7-36"))] - flags: flags as u32, - #[cfg(feature = "abi-7-36")] flags: (flags | FUSE_INIT_EXT) as u32, max_background: config.max_background, congestion_threshold: config.congestion_threshold(), max_write: config.max_write, - #[cfg(feature = "abi-7-23")] time_gran: config.time_gran.as_nanos() as u32, - #[cfg(all(feature = "abi-7-23", not(feature = "abi-7-28")))] - reserved: [0; 9], - #[cfg(feature = "abi-7-28")] max_pages: config.max_pages(), - #[cfg(feature = "abi-7-28")] unused2: 0, - #[cfg(all(feature = "abi-7-28", not(feature = "abi-7-36")))] - reserved: [0; 8], - #[cfg(feature = "abi-7-36")] flags2: (flags >> 32) as u32, - #[cfg(all(feature = "abi-7-36", not(feature = "abi-7-40")))] reserved: [0; 7], - #[cfg(feature = "abi-7-40")] - max_stack_depth: config.max_stack_depth, - #[cfg(feature = "abi-7-40")] - reserved: [0; 6], }; Response::new_data(init.as_bytes()) } @@ -1006,34 +884,34 @@ mod op { /// /// TODO: Document how to implement "standard conforming directory stream operations" #[derive(Debug)] - pub struct OpenDir<'a> { + pub(crate) struct OpenDir<'a> { header: &'a fuse_in_header, arg: &'a fuse_open_in, } impl_request!(OpenDir<'a>); impl OpenDir<'_> { /// Flags as passed to open - pub fn flags(&self) -> i32 { + pub(crate) fn flags(&self) -> i32 { self.arg.flags } } /// Read directory. #[derive(Debug)] - pub struct ReadDir<'a> { + pub(crate) struct ReadDir<'a> { header: &'a fuse_in_header, arg: &'a fuse_read_in, } impl_request!(ReadDir<'a>); impl ReadDir<'_> { /// The value set by the [`OpenDir`] method. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } - pub fn offset(&self) -> i64 { + pub(crate) fn offset(&self) -> i64 { self.arg.offset } - pub fn size(&self) -> u32 { + pub(crate) fn size(&self) -> u32 { self.arg.size } } @@ -1042,20 +920,20 @@ mod op { /// /// For every [`OpenDir`] call there will be exactly one [`ReleaseDir`] call. #[derive(Debug)] - pub struct ReleaseDir<'a> { + pub(crate) struct ReleaseDir<'a> { header: &'a fuse_in_header, arg: &'a fuse_release_in, } impl_request!(ReleaseDir<'a>); impl ReleaseDir<'_> { /// The value set by the [`OpenDir`] method. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } - pub fn flush(&self) -> bool { + pub(crate) fn flush(&self) -> bool { self.arg.release_flags & consts::FUSE_RELEASE_FLUSH != 0 } - pub fn lock_owner(&self) -> Option { + pub(crate) fn lock_owner(&self) -> Option { if self.arg.release_flags & FUSE_RELEASE_FLOCK_UNLOCK != 0 { Some(LockOwner(self.arg.lock_owner)) } else { @@ -1063,45 +941,45 @@ mod op { } } /// TODO: Document what values this may take - pub fn flags(&self) -> i32 { + pub(crate) fn flags(&self) -> i32 { self.arg.flags } } /// Synchronize directory contents. #[derive(Debug)] - pub struct FSyncDir<'a> { + pub(crate) struct FSyncDir<'a> { header: &'a fuse_in_header, arg: &'a fuse_fsync_in, } impl_request!(FSyncDir<'a>); impl FSyncDir<'_> { /// The value set by the [`OpenDir`] method. See [`FileHandle`]. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } /// If set, then only the directory contents should be flushed, not the meta data. - pub fn fdatasync(&self) -> bool { + pub(crate) fn fdatasync(&self) -> bool { self.arg.fsync_flags & consts::FUSE_FSYNC_FDATASYNC != 0 } } /// Test for a POSIX file lock. #[derive(Debug)] - pub struct GetLk<'a> { + pub(crate) struct GetLk<'a> { header: &'a fuse_in_header, arg: &'a fuse_lk_in, } impl_request!(GetLk<'a>); impl GetLk<'_> { /// The value set by the [`Open`] method. See [`FileHandle`]. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } - pub fn lock(&self) -> Lock { + pub(crate) fn lock(&self) -> Lock { Lock::from_abi(&self.arg.lk) } - pub fn lock_owner(&self) -> LockOwner { + pub(crate) fn lock_owner(&self) -> LockOwner { LockOwner(self.arg.owner) } } @@ -1115,38 +993,38 @@ mod op { /// implemented, the kernel will still allow file locking to work locally. /// Hence these are only interesting for network filesystems and similar. #[derive(Debug)] - pub struct SetLk<'a> { + pub(crate) struct SetLk<'a> { header: &'a fuse_in_header, arg: &'a fuse_lk_in, } impl_request!(SetLk<'a>); impl SetLk<'_> { /// The value set by the [`Open`] method. See [`FileHandle`]. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } - pub fn lock(&self) -> Lock { + pub(crate) fn lock(&self) -> Lock { Lock::from_abi(&self.arg.lk) } - pub fn lock_owner(&self) -> LockOwner { + pub(crate) fn lock_owner(&self) -> LockOwner { LockOwner(self.arg.owner) } } #[derive(Debug)] - pub struct SetLkW<'a> { + pub(crate) struct SetLkW<'a> { header: &'a fuse_in_header, arg: &'a fuse_lk_in, } impl_request!(SetLkW<'a>); impl SetLkW<'_> { /// The value set by the [`Open`] method. See [`FileHandle`]. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } - pub fn lock(&self) -> Lock { + pub(crate) fn lock(&self) -> Lock { Lock::from_abi(&self.arg.lk) } - pub fn lock_owner(&self) -> LockOwner { + pub(crate) fn lock_owner(&self) -> LockOwner { LockOwner(self.arg.owner) } } @@ -1156,13 +1034,13 @@ mod op { /// This will be called for the `access()` system call. If the `default_permissions` /// mount option is given, this method is not called. #[derive(Debug)] - pub struct Access<'a> { + pub(crate) struct Access<'a> { header: &'a fuse_in_header, arg: &'a fuse_access_in, } impl_request!(Access<'a>); impl Access<'_> { - pub fn mask(&self) -> i32 { + pub(crate) fn mask(&self) -> i32 { self.arg.mask } } @@ -1179,24 +1057,24 @@ mod op { /// implemented or under Linux kernel versions earlier than 2.6.15, the [`MkNod`] /// and [`Open`] methods will be called instead. #[derive(Debug)] - pub struct Create<'a> { + pub(crate) struct Create<'a> { header: &'a fuse_in_header, arg: &'a fuse_create_in, name: &'a Path, } impl_request!(Create<'a>); impl<'a> Create<'a> { - pub fn name(&self) -> &'a Path { + pub(crate) fn name(&self) -> &'a Path { self.name } - pub fn mode(&self) -> u32 { + pub(crate) fn mode(&self) -> u32 { self.arg.mode } /// Flags as passed to the `create()` call - pub fn flags(&self) -> i32 { + pub(crate) fn flags(&self) -> i32 { self.arg.flags } - pub fn umask(&self) -> u32 { + pub(crate) fn umask(&self) -> u32 { self.arg.umask } } @@ -1239,13 +1117,13 @@ mod op { /// In case (1) the [`Interrupt`] request will be requeued. In case (2) the /// [`Interrupt`] reply will be ignored. #[derive(Debug)] - pub struct Interrupt<'a> { + pub(crate) struct Interrupt<'a> { header: &'a fuse_in_header, arg: &'a fuse_interrupt_in, } impl_request!(Interrupt<'a>); impl Interrupt<'_> { - pub fn unique(&self) -> RequestId { + pub(crate) fn unique(&self) -> RequestId { RequestId(self.arg.unique) } } @@ -1254,107 +1132,95 @@ mod op { /// Note: This makes sense only for block device backed filesystems mounted /// with the `blkdev` option #[derive(Debug)] - pub struct BMap<'a> { + pub(crate) struct BMap<'a> { header: &'a fuse_in_header, arg: &'a fuse_bmap_in, } impl_request!(BMap<'a>); impl BMap<'_> { - pub fn block_size(&self) -> u32 { + pub(crate) fn block_size(&self) -> u32 { self.arg.blocksize } - pub fn block(&self) -> u64 { + pub(crate) fn block(&self) -> u64 { self.arg.block } } #[derive(Debug)] - pub struct Destroy<'a> { + pub(crate) struct Destroy<'a> { header: &'a fuse_in_header, } impl_request!(Destroy<'a>); impl<'a> Destroy<'a> { - pub fn reply(&self) -> Response<'a> { + pub(crate) fn reply(&self) -> Response<'a> { Response::new_empty() } } /// Control device #[derive(Debug)] - pub struct IoCtl<'a> { + pub(crate) struct IoCtl<'a> { header: &'a fuse_in_header, arg: &'a fuse_ioctl_in, data: &'a [u8], } impl_request!(IoCtl<'a>); impl IoCtl<'_> { - pub fn in_data(&self) -> &[u8] { + pub(crate) fn in_data(&self) -> &[u8] { &self.data[..self.arg.in_size as usize] } - pub fn unrestricted(&self) -> bool { + pub(crate) fn unrestricted(&self) -> bool { self.arg.flags & consts::FUSE_IOCTL_UNRESTRICTED != 0 } /// The value set by the [`Open`] method. See [`FileHandle`]. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } /// TODO: What are valid values here? - pub fn flags(&self) -> u32 { + pub(crate) fn flags(&self) -> u32 { self.arg.flags } /// TODO: What does this mean? - pub fn command(&self) -> u32 { + pub(crate) fn command(&self) -> u32 { self.arg.cmd } - pub fn out_size(&self) -> u32 { + pub(crate) fn out_size(&self) -> u32 { self.arg.out_size } } /// Poll. #[derive(Debug)] - pub struct Poll<'a> { + pub(crate) struct Poll<'a> { header: &'a fuse_in_header, arg: &'a fuse_poll_in, } impl_request!(Poll<'a>); impl Poll<'_> { /// The value set by the [`Open`] method. See [`FileHandle`]. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } /// The unique id used for the poll context by the kernel - pub fn kernel_handle(&self) -> u64 { + pub(crate) fn kernel_handle(&self) -> u64 { self.arg.kh } /// The requested poll events - pub fn events(&self) -> u32 { - #[cfg(feature = "abi-7-21")] - return self.arg.events; - #[cfg(not(feature = "abi-7-21"))] - return 0; + pub(crate) fn events(&self) -> u32 { + self.arg.events } /// The poll request's flags - pub fn flags(&self) -> u32 { + pub(crate) fn flags(&self) -> u32 { self.arg.flags } } - /// `NotifyReply`. TODO: currently unsupported by fuser - #[derive(Debug)] - pub struct NotifyReply<'a> { - header: &'a fuse_in_header, - #[allow(unused)] - arg: &'a [u8], - } - impl_request!(NotifyReply<'a>); - /// `BatchForget`: TODO: merge with Forget #[derive(Debug)] - pub struct BatchForget<'a> { + pub(crate) struct BatchForget<'a> { header: &'a fuse_in_header, #[allow(unused)] arg: &'a fuse_batch_forget_in, @@ -1363,7 +1229,7 @@ mod op { impl_request!(BatchForget<'a>); impl<'a> BatchForget<'a> { /// TODO: Don't return `fuse_forget_one`, this should be private - pub fn nodes(&self) -> &'a [fuse_forget_one] { + pub(crate) fn nodes(&self) -> &'a [fuse_forget_one] { self.nodes } } @@ -1371,28 +1237,25 @@ mod op { /// Preallocate or deallocate space to a file /// /// Implementations should return EINVAL if offset or length are < 0 - #[cfg(feature = "abi-7-19")] #[derive(Debug)] - pub struct FAllocate<'a> { + pub(crate) struct FAllocate<'a> { header: &'a fuse_in_header, arg: &'a fuse_fallocate_in, } - #[cfg(feature = "abi-7-19")] impl_request!(FAllocate<'a>); - #[cfg(feature = "abi-7-19")] impl FAllocate<'_> { /// The value set by the [`Open`] method. See [`FileHandle`]. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } - pub fn offset(&self) -> i64 { + pub(crate) fn offset(&self) -> i64 { self.arg.offset } - pub fn len(&self) -> i64 { + pub(crate) fn len(&self) -> i64 { self.arg.length } /// `mode` as passed to fallocate. See `man 2 fallocate` - pub fn mode(&self) -> i32 { + pub(crate) fn mode(&self) -> i32 { self.arg.mode } } @@ -1400,51 +1263,45 @@ mod op { /// Read directory. /// /// TODO: Document when this is called rather than `ReadDir` - #[cfg(feature = "abi-7-21")] #[derive(Debug)] - pub struct ReadDirPlus<'a> { + pub(crate) struct ReadDirPlus<'a> { header: &'a fuse_in_header, arg: &'a fuse_read_in, } - #[cfg(feature = "abi-7-21")] impl_request!(ReadDirPlus<'a>); - #[cfg(feature = "abi-7-21")] impl ReadDirPlus<'_> { /// The value set by the [`Open`] method. See [`FileHandle`]. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } - pub fn offset(&self) -> i64 { + pub(crate) fn offset(&self) -> i64 { self.arg.offset } - pub fn size(&self) -> u32 { + pub(crate) fn size(&self) -> u32 { self.arg.size } } /// Rename a file. /// - /// TODO: Document the differences to [`Rename`] and [`Exchange`] - #[cfg(feature = "abi-7-23")] + /// TODO: Document the differences to [`Rename`] #[derive(Debug)] - pub struct Rename2<'a> { + pub(crate) struct Rename2<'a> { header: &'a fuse_in_header, arg: &'a fuse_rename2_in, name: &'a Path, newname: &'a Path, old_parent: INodeNo, } - #[cfg(feature = "abi-7-23")] impl_request!(Rename2<'a>); - #[cfg(feature = "abi-7-23")] impl<'a> Rename2<'a> { - pub fn from(&self) -> FilenameInDir<'a> { + pub(crate) fn from(&self) -> FilenameInDir<'a> { FilenameInDir::<'a> { dir: self.old_parent, name: self.name, } } - pub fn to(&self) -> FilenameInDir<'a> { + pub(crate) fn to(&self) -> FilenameInDir<'a> { FilenameInDir::<'a> { dir: INodeNo(self.arg.newdir), name: self.newname, @@ -1456,7 +1313,7 @@ mod op { /// reply with an EINVAL error. /// /// TODO: Replace with enum/flags type - pub fn flags(&self) -> u32 { + pub(crate) fn flags(&self) -> u32 { self.arg.flags } } @@ -1464,50 +1321,43 @@ mod op { /// Reposition read/write file offset /// /// TODO: Document when you need to implement this. Read and Write provide the offset anyway. - #[cfg(feature = "abi-7-24")] #[derive(Debug)] - pub struct Lseek<'a> { + pub(crate) struct Lseek<'a> { header: &'a fuse_in_header, arg: &'a fuse_lseek_in, } - #[cfg(feature = "abi-7-24")] impl_request!(Lseek<'a>); - #[cfg(feature = "abi-7-24")] impl Lseek<'_> { /// The value set by the [`Open`] method. See [`FileHandle`]. - pub fn file_handle(&self) -> FileHandle { + pub(crate) fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } - pub fn offset(&self) -> i64 { + pub(crate) fn offset(&self) -> i64 { self.arg.offset } /// TODO: Make this return an enum - pub fn whence(&self) -> i32 { + pub(crate) fn whence(&self) -> i32 { self.arg.whence } } /// Copy the specified range from the source inode to the destination inode - #[cfg(feature = "abi-7-28")] #[derive(Debug, Clone, Copy)] - pub struct CopyFileRangeFile { - pub inode: INodeNo, + pub(crate) struct CopyFileRangeFile { + pub(crate) inode: INodeNo, /// The value set by the [`Open`] method. See [`FileHandle`]. - pub file_handle: FileHandle, - pub offset: i64, + pub(crate) file_handle: FileHandle, + pub(crate) offset: i64, } - #[cfg(feature = "abi-7-28")] #[derive(Debug)] - pub struct CopyFileRange<'a> { + pub(crate) struct CopyFileRange<'a> { header: &'a fuse_in_header, arg: &'a fuse_copy_file_range_in, } - #[cfg(feature = "abi-7-28")] impl_request!(CopyFileRange<'a>); - #[cfg(feature = "abi-7-28")] impl CopyFileRange<'_> { /// File and offset to copy data from - pub fn src(&self) -> CopyFileRangeFile { + pub(crate) fn src(&self) -> CopyFileRangeFile { CopyFileRangeFile { inode: self.nodeid(), file_handle: FileHandle(self.arg.fh_in), @@ -1515,7 +1365,7 @@ mod op { } } /// File and offset to copy data to - pub fn dest(&self) -> CopyFileRangeFile { + pub(crate) fn dest(&self) -> CopyFileRangeFile { CopyFileRangeFile { inode: INodeNo(self.arg.nodeid_out), file_handle: FileHandle(self.arg.fh_out), @@ -1523,80 +1373,15 @@ mod op { } } /// Number of bytes to copy - pub fn len(&self) -> u64 { + pub(crate) fn len(&self) -> u64 { self.arg.len } // API TODO: Return a specific flags type - pub fn flags(&self) -> u64 { + pub(crate) fn flags(&self) -> u64 { self.arg.flags } } - /// `MacOS` only: Rename the volume. Set `fuse_init_out.flags` during init to - /// `FUSE_VOL_RENAME` to enable - #[cfg(target_os = "macos")] - #[derive(Debug)] - pub struct SetVolName<'a> { - header: &'a fuse_in_header, - name: &'a OsStr, - } - #[cfg(target_os = "macos")] - impl_request!(SetVolName<'a>); - #[cfg(target_os = "macos")] - impl<'a> SetVolName<'a> { - pub fn name(&self) -> &'a OsStr { - self.name - } - } - - /// `MacOS` only: Query extended times (`bkuptime` and `crtime`). Set `fuse_init_out.flags` - /// during init to `FUSE_XTIMES` to enable - #[cfg(target_os = "macos")] - #[derive(Debug)] - pub struct GetXTimes<'a> { - header: &'a fuse_in_header, - } - #[cfg(target_os = "macos")] - impl_request!(GetXTimes<'a>); - // API TODO: Consider `rename2(RENAME_EXCHANGE)` - /// `MacOS` only (undocumented) - #[cfg(target_os = "macos")] - #[derive(Debug)] - pub struct Exchange<'a> { - header: &'a fuse_in_header, - arg: &'a fuse_exchange_in, - oldname: &'a Path, - newname: &'a Path, - } - #[cfg(target_os = "macos")] - impl_request!(Exchange<'a>); - #[cfg(target_os = "macos")] - impl<'a> Exchange<'a> { - pub fn from(&self) -> FilenameInDir<'a> { - FilenameInDir::<'a> { - dir: INodeNo(self.arg.olddir), - name: self.oldname, - } - } - pub fn to(&self) -> FilenameInDir<'a> { - FilenameInDir::<'a> { - dir: INodeNo(self.arg.newdir), - name: self.newname, - } - } - pub fn options(&self) -> u64 { - self.arg.options - } - } - /// TODO: Document - #[derive(Debug)] - pub struct CuseInit<'a> { - header: &'a fuse_in_header, - #[allow(unused)] - arg: &'a fuse_init_in, - } - impl_request!(CuseInit<'a>); - fn system_time_from_time(secs: i64, nsecs: u32) -> SystemTime { if secs >= 0 { SystemTime::UNIX_EPOCH + Duration::new(secs as u64, nsecs) @@ -1628,7 +1413,7 @@ mod op { header, arg: data.fetch()?, }), - fuse_opcode::FUSE_READLINK => Operation::ReadLink(ReadLink { header }), + fuse_opcode::FUSE_READLINK => Operation::ReadLink, fuse_opcode::FUSE_SYMLINK => Operation::SymLink(SymLink { header, link_name: data.fetch_str()?.as_ref(), @@ -1680,7 +1465,7 @@ mod op { assert!(out.data().len() == out.arg.size as usize); out }), - fuse_opcode::FUSE_STATFS => Operation::StatFs(StatFs { header }), + fuse_opcode::FUSE_STATFS => Operation::StatFs, fuse_opcode::FUSE_RELEASE => Operation::Release(Release { header, arg: data.fetch()?, @@ -1775,10 +1560,6 @@ mod op { header, arg: data.fetch()?, }), - fuse_opcode::FUSE_NOTIFY_REPLY => Operation::NotifyReply(NotifyReply { - header, - arg: data.fetch_all(), - }), fuse_opcode::FUSE_BATCH_FORGET => { let arg = data.fetch()?; Operation::BatchForget(BatchForget { @@ -1787,17 +1568,14 @@ mod op { nodes: data.fetch_slice(arg.count as usize)?, }) } - #[cfg(feature = "abi-7-19")] fuse_opcode::FUSE_FALLOCATE => Operation::FAllocate(FAllocate { header, arg: data.fetch()?, }), - #[cfg(feature = "abi-7-21")] fuse_opcode::FUSE_READDIRPLUS => Operation::ReadDirPlus(ReadDirPlus { header, arg: data.fetch()?, }), - #[cfg(feature = "abi-7-23")] fuse_opcode::FUSE_RENAME2 => Operation::Rename2(Rename2 { header, arg: data.fetch()?, @@ -1805,36 +1583,14 @@ mod op { newname: data.fetch_str()?.as_ref(), old_parent: INodeNo(header.nodeid), }), - #[cfg(feature = "abi-7-24")] fuse_opcode::FUSE_LSEEK => Operation::Lseek(Lseek { header, arg: data.fetch()?, }), - #[cfg(feature = "abi-7-28")] fuse_opcode::FUSE_COPY_FILE_RANGE => Operation::CopyFileRange(CopyFileRange { header, arg: data.fetch()?, }), - - #[cfg(target_os = "macos")] - fuse_opcode::FUSE_SETVOLNAME => Operation::SetVolName(SetVolName { - header, - name: data.fetch_str()?, - }), - #[cfg(target_os = "macos")] - fuse_opcode::FUSE_GETXTIMES => Operation::GetXTimes(GetXTimes { header }), - #[cfg(target_os = "macos")] - fuse_opcode::FUSE_EXCHANGE => Operation::Exchange(Exchange { - header, - arg: data.fetch()?, - oldname: data.fetch_str()?.as_ref(), - newname: data.fetch_str()?.as_ref(), - }), - - fuse_opcode::CUSE_INIT => Operation::CuseInit(CuseInit { - header, - arg: data.fetch()?, - }), }) } } @@ -1844,13 +1600,12 @@ use op::*; /// variant needs to match the actual arguments the kernel driver sends for the specific operation. #[derive(Debug)] #[allow(missing_docs)] -pub enum Operation<'a> { +pub(crate) enum Operation<'a> { Lookup(Lookup<'a>), Forget(Forget<'a>), GetAttr(GetAttr<'a>), SetAttr(SetAttr<'a>), - #[allow(dead_code)] - ReadLink(ReadLink<'a>), + ReadLink, SymLink(SymLink<'a>), MkNod(MkNod<'a>), MkDir(MkDir<'a>), @@ -1861,8 +1616,7 @@ pub enum Operation<'a> { Open(Open<'a>), Read(Read<'a>), Write(Write<'a>), - #[allow(dead_code)] - StatFs(StatFs<'a>), + StatFs, Release(Release<'a>), FSync(FSync<'a>), SetXAttr(SetXAttr<'a>), @@ -1885,29 +1639,12 @@ pub enum Operation<'a> { Destroy(Destroy<'a>), IoCtl(IoCtl<'a>), Poll(Poll<'a>), - #[allow(dead_code)] - NotifyReply(NotifyReply<'a>), BatchForget(BatchForget<'a>), - #[cfg(feature = "abi-7-19")] FAllocate(FAllocate<'a>), - #[cfg(feature = "abi-7-21")] ReadDirPlus(ReadDirPlus<'a>), - #[cfg(feature = "abi-7-23")] Rename2(Rename2<'a>), - #[cfg(feature = "abi-7-24")] Lseek(Lseek<'a>), - #[cfg(feature = "abi-7-28")] CopyFileRange(CopyFileRange<'a>), - - #[cfg(target_os = "macos")] - SetVolName(SetVolName<'a>), - #[cfg(target_os = "macos")] - GetXTimes(GetXTimes<'a>), - #[cfg(target_os = "macos")] - Exchange(Exchange<'a>), - - #[allow(dead_code)] - CuseInit(CuseInit<'a>), } impl fmt::Display for Operation<'_> { @@ -1917,7 +1654,7 @@ impl fmt::Display for Operation<'_> { Operation::Forget(x) => write!(f, "FORGET nlookup {}", x.nlookup()), Operation::GetAttr(_) => write!(f, "GETATTR"), Operation::SetAttr(x) => x.fmt(f), - Operation::ReadLink(_) => write!(f, "READLINK"), + Operation::ReadLink => write!(f, "READLINK"), Operation::SymLink(x) => { write!( f, @@ -1954,7 +1691,7 @@ impl fmt::Display for Operation<'_> { x.data().len(), x.write_flags() ), - Operation::StatFs(_) => write!(f, "STATFS"), + Operation::StatFs => write!(f, "STATFS"), Operation::Release(x) => write!( f, "RELEASE fh {:?}, flags {:#x}, flush {}, lock owner {:?}", @@ -1977,7 +1714,7 @@ impl fmt::Display for Operation<'_> { x.flags() ), Operation::GetXAttr(x) => { - write!(f, "GETXATTR name {:?}, size {:?}", x.name(), x.size()) + write!(f, "GETXATTR name {:?}, size {:?}", x.name(), x.size_u32()) } Operation::ListXAttr(x) => write!(f, "LISTXATTR size {}", x.size()), Operation::RemoveXAttr(x) => write!(f, "REMOVEXATTR name {:?}", x.name()), @@ -2054,11 +1791,8 @@ impl fmt::Display for Operation<'_> { x.flags() ), Operation::Poll(x) => write!(f, "POLL fh {:?}", x.file_handle()), - Operation::NotifyReply(_) => write!(f, "NOTIFYREPLY"), Operation::BatchForget(x) => write!(f, "BATCHFORGET nodes {:?}", x.nodes()), - #[cfg(feature = "abi-7-19")] Operation::FAllocate(_) => write!(f, "FALLOCATE"), - #[cfg(feature = "abi-7-21")] Operation::ReadDirPlus(x) => write!( f, "READDIRPLUS fh {:?}, offset {}, size {}", @@ -2066,9 +1800,7 @@ impl fmt::Display for Operation<'_> { x.offset(), x.size() ), - #[cfg(feature = "abi-7-23")] Operation::Rename2(x) => write!(f, "RENAME2 from {:?}, to {:?}", x.from(), x.to()), - #[cfg(feature = "abi-7-24")] Operation::Lseek(x) => write!( f, "LSEEK fh {:?}, offset {}, whence {}", @@ -2076,7 +1808,6 @@ impl fmt::Display for Operation<'_> { x.offset(), x.whence() ), - #[cfg(feature = "abi-7-28")] Operation::CopyFileRange(x) => write!( f, "COPY_FILE_RANGE src {:?}, dest {:?}, len {}", @@ -2084,35 +1815,20 @@ impl fmt::Display for Operation<'_> { x.dest(), x.len() ), - - #[cfg(target_os = "macos")] - Operation::SetVolName(x) => write!(f, "SETVOLNAME name {:?}", x.name()), - #[cfg(target_os = "macos")] - Operation::GetXTimes(_) => write!(f, "GETXTIMES"), - #[cfg(target_os = "macos")] - Operation::Exchange(x) => write!( - f, - "EXCHANGE from {:?}, to {:?}, options {:#x}", - x.from(), - x.to(), - x.options() - ), - - Operation::CuseInit(_) => write!(f, "CUSE_INIT"), } } } /// Low-level request of a filesystem operation the kernel driver wants to perform. #[derive(Debug)] -pub struct AnyRequest<'a> { +pub(crate) struct AnyRequest<'a> { header: &'a fuse_in_header, data: &'a [u8], } impl_request!(AnyRequest<'_>); impl<'a> AnyRequest<'a> { - pub fn operation(&self) -> Result, RequestError> { + pub(crate) fn operation(&self) -> Result, RequestError> { // Parse/check opcode let opcode = fuse_opcode::try_from(self.header.opcode) .map_err(|_: InvalidOpcodeError| RequestError::UnknownOperation(self.header.opcode))?; @@ -2168,31 +1884,7 @@ mod tests { use super::*; use std::ffi::OsStr; - #[cfg(all(target_endian = "big", not(feature = "abi-7-36")))] - const INIT_REQUEST: AlignedData<[u8; 56]> = AlignedData([ - // decimal 56 == hex 0x38 - 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1a, // len, opcode - 0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid - 0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, // uid, gid - 0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, // pid, padding - 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, // major, minor - 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags - ]); - - #[cfg(all(target_endian = "little", not(feature = "abi-7-36")))] - const INIT_REQUEST: AlignedData<[u8; 56]> = AlignedData([ - // decimal 56 == hex 0x38 - 0x38, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, // len, opcode - 0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, // unique - 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // nodeid - 0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, // uid, gid - 0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, // pid, padding - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // major, minor - 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags - ]); - - #[cfg(all(target_endian = "big", feature = "abi-7-36"))] + #[cfg(target_endian = "big")] const INIT_REQUEST: AlignedData<[u8; 104]> = AlignedData([ // decimal 104 == hex 0x68 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x1a, // len, opcode @@ -2209,7 +1901,7 @@ mod tests { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]); - #[cfg(all(target_endian = "little", feature = "abi-7-36"))] + #[cfg(target_endian = "little")] const INIT_REQUEST: AlignedData<[u8; 104]> = AlignedData([ // decimal 104 == hex 0x68 0x68, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, // len, opcode @@ -2260,9 +1952,6 @@ mod tests { #[test] fn short_read() { match AnyRequest::try_from(&INIT_REQUEST[..48]) { - #[cfg(not(feature = "abi-7-36"))] - Err(RequestError::ShortRead(48, 56)) => (), - #[cfg(feature = "abi-7-36")] Err(RequestError::ShortRead(48, 104)) => (), _ => panic!("Unexpected request parsing result"), } @@ -2271,9 +1960,6 @@ mod tests { #[test] fn init() { let req = AnyRequest::try_from(&INIT_REQUEST[..]).unwrap(); - #[cfg(not(feature = "abi-7-36"))] - assert_eq!(req.header.len, 56); - #[cfg(feature = "abi-7-36")] assert_eq!(req.header.len, 104); assert_eq!(req.header.opcode, 26); assert_eq!(req.unique(), RequestId(0xdead_beef_baad_f00d)); diff --git a/cli/src/fuser/mnt/fuse_pure.rs b/crates/agentfs-fuse/src/transport/mnt/fuse_pure.rs similarity index 56% rename from cli/src/fuser/mnt/fuse_pure.rs rename to crates/agentfs-fuse/src/transport/mnt/fuse_pure.rs index 74192288..a8465e03 100644 --- a/cli/src/fuser/mnt/fuse_pure.rs +++ b/crates/agentfs-fuse/src/transport/mnt/fuse_pure.rs @@ -9,55 +9,56 @@ use super::is_mounted; use super::mount_options::{option_to_string, MountOption}; use libc::c_int; -use log::{debug, error}; use std::ffi::{CStr, CString, OsStr}; use std::fs::File; -#[cfg(any( - target_os = "linux", - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "openbsd", - target_os = "netbsd", -))] use std::fs::OpenOptions; use std::io; use std::io::{Error, ErrorKind, Read}; use std::os::unix::ffi::OsStrExt; -#[cfg(any(target_os = "linux", target_os = "macos"))] use std::os::unix::fs::PermissionsExt; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::os::unix::net::UnixStream; use std::path::Path; use std::process::{Command, Stdio}; -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; +use std::time::{Duration, Instant}; use std::{mem, ptr}; +use tracing::{debug, error, warn}; const FUSERMOUNT_BIN: &str = "fusermount"; const FUSERMOUNT3_BIN: &str = "fusermount3"; const FUSERMOUNT_COMM_ENV: &str = "_FUSE_COMMFD"; -const MOUNT_FUSEFS_BIN: &str = "mount_fusefs"; - -#[cfg_attr(target_os = "freebsd", allow(dead_code))] -#[cfg(target_os = "freebsd")] -const BSD_MNT_NODEV: libc::c_int = 0x0000_0010; -#[cfg_attr(target_os = "freebsd", allow(dead_code))] -#[cfg(any( - target_os = "dragonfly", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", -))] -const BSD_MNT_NODEV: libc::c_int = libc::MNT_NODEV; + +/// On kernels with fuse-over-io_uring enabled, a mount(2) that races the ~2s +/// kernel-side drain of a just-closed FUSE connection blocks forever +/// (observed on 7.1.2-cachyos). fusermount performs that mount(2) for +/// unprivileged callers, so its reply wait is bounded and retried on such +/// kernels; the drain clears within roughly two seconds, well inside the +/// retry budget. +const FUSERMOUNT_REPLY_TIMEOUT: Duration = Duration::from_secs(3); +const URING_DRAIN_MOUNT_ATTEMPTS: u32 = 3; +const URING_DRAIN_RETRY_BACKOFF: Duration = Duration::from_millis(750); + +fn kernel_fuse_uring_enabled() -> bool { + static ENABLED: OnceLock = OnceLock::new(); + *ENABLED.get_or_init(|| { + std::fs::read_to_string("/sys/module/fuse/parameters/enable_uring") + .map(|value| value.trim() == "Y") + .unwrap_or(false) + }) +} #[derive(Debug)] -pub struct Mount { +pub(crate) struct Mount { mountpoint: CString, auto_unmount_socket: Option, fuse_device: Arc, } impl Mount { - pub fn new(mountpoint: &Path, options: &[MountOption]) -> io::Result<(Arc, Mount)> { + pub(crate) fn new( + mountpoint: &Path, + options: &[MountOption], + ) -> io::Result<(Arc, Mount)> { let mountpoint = mountpoint.canonicalize()?; let (file, sock) = fuse_mount_pure(mountpoint.as_os_str(), options)?; let file = Arc::new(file); @@ -107,23 +108,12 @@ fn fuse_mount_pure( return fuse_mount_fusermount(mountpoint, options); } - // The direct mount path is currently implemented only for Linux and macOS. - // Other supported Unix targets (such as the BSDs) rely on the setuid - // mount helper, which mirrors libfuse's approach. - #[cfg(not(any(target_os = "linux", target_os = "macos")))] - { - return fuse_mount_fusermount(mountpoint, options); - } - - #[cfg(any(target_os = "linux", target_os = "macos"))] - { - let res = fuse_mount_sys(mountpoint, options)?; - match res { - Some(file) => Ok((file, None)), - _ => { - // Retry - fuse_mount_fusermount(mountpoint, options) - } + let res = fuse_mount_sys(mountpoint, options)?; + match res { + Some(file) => Ok((file, None)), + _ => { + // Retry + fuse_mount_fusermount(mountpoint, options) } } } @@ -136,14 +126,6 @@ fn fuse_unmount_pure(mountpoint: &CStr) { return; } } - #[cfg(target_os = "macos")] - unsafe { - let result = libc::unmount(mountpoint.as_ptr(), libc::MNT_FORCE); - if result == 0 { - return; - } - } - let mut builder = Command::new(detect_fusermount_bin()); builder.stdout(Stdio::piped()).stderr(Stdio::piped()); builder @@ -163,10 +145,8 @@ fn detect_fusermount_bin() -> String { for name in [ FUSERMOUNT3_BIN.to_string(), FUSERMOUNT_BIN.to_string(), - MOUNT_FUSEFS_BIN.to_string(), format!("/sbin/{FUSERMOUNT3_BIN}"), format!("/sbin/{FUSERMOUNT_BIN}"), - format!("/sbin/{MOUNT_FUSEFS_BIN}"), format!("/bin/{FUSERMOUNT3_BIN}"), format!("/bin/{FUSERMOUNT_BIN}"), ] @@ -180,7 +160,44 @@ fn detect_fusermount_bin() -> String { FUSERMOUNT3_BIN.to_string() } -fn receive_fusermount_message(socket: &UnixStream) -> Result { +/// Wait until `socket` is readable, failing with `ErrorKind::TimedOut` once +/// the deadline passes. +fn wait_for_fusermount_reply(socket: &UnixStream, timeout: Duration) -> Result<(), Error> { + let deadline = Instant::now() + timeout; + loop { + let remaining = deadline.saturating_duration_since(Instant::now()); + let mut poll_fd = libc::pollfd { + fd: socket.as_raw_fd(), + events: libc::POLLIN, + revents: 0, + }; + let millis = remaining.as_millis().min(i32::MAX as u128) as c_int; + let result = unsafe { libc::poll(&mut poll_fd, 1, millis) }; + match result { + 0 => { + return Err(Error::new( + ErrorKind::TimedOut, + format!("no reply from fusermount within {timeout:?}"), + )) + } + 1 => return Ok(()), + _ => { + let err = Error::last_os_error(); + if err.kind() != ErrorKind::Interrupted { + return Err(err); + } + } + } + } +} + +fn receive_fusermount_message( + socket: &UnixStream, + reply_timeout: Option, +) -> Result { + if let Some(timeout) = reply_timeout { + wait_for_fusermount_reply(socket, timeout)?; + } let mut io_vec_buf = [0u8]; let mut io_vec = libc::iovec { iov_base: io_vec_buf.as_mut_ptr() as *mut libc::c_void, @@ -212,25 +229,6 @@ fn receive_fusermount_message(socket: &UnixStream) -> Result { message.msg_controllen = cmsg_buffer.len() as u32; message.msg_flags = 0; } - #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "openbsd", - target_os = "netbsd" - ))] - { - message = libc::msghdr { - msg_name: ptr::null_mut(), - msg_namelen: 0, - msg_iov: &mut io_vec, - msg_iovlen: 1, - msg_control: (&mut cmsg_buffer).as_mut_ptr() as *mut libc::c_void, - msg_controllen: cmsg_buffer.len() as u32, - msg_flags: 0, - }; - } - let mut result; loop { unsafe { @@ -277,11 +275,50 @@ fn fuse_mount_fusermount( mountpoint: &OsStr, options: &[MountOption], ) -> Result<(File, Option), Error> { - let fusermount_bin = detect_fusermount_bin(); + // Only kernels with fuse-over-io_uring enabled are known to wedge a fresh + // mount(2) (the connection-drain race documented above); everything else + // keeps the historical unbounded wait for fusermount's reply. + if !kernel_fuse_uring_enabled() { + return fuse_mount_fusermount_once(mountpoint, options, None); + } - if fusermount_bin.ends_with(MOUNT_FUSEFS_BIN) { - return fuse_mount_mount_fusefs(&fusermount_bin, mountpoint, options); + let mut attempt = 1u32; + loop { + match fuse_mount_fusermount_once(mountpoint, options, Some(FUSERMOUNT_REPLY_TIMEOUT)) { + Err(err) + if err.kind() == ErrorKind::TimedOut && attempt < URING_DRAIN_MOUNT_ATTEMPTS => + { + warn!( + "fusermount did not reply within {FUSERMOUNT_REPLY_TIMEOUT:?} \ + (attempt {attempt}/{URING_DRAIN_MOUNT_ATTEMPTS}); likely racing the \ + kernel-side drain of a just-closed fuse-over-io_uring connection; retrying" + ); + attempt += 1; + std::thread::sleep(URING_DRAIN_RETRY_BACKOFF); + } + Err(err) if err.kind() == ErrorKind::TimedOut => { + return Err(Error::new( + ErrorKind::TimedOut, + format!( + "FUSE mount at {mountpoint:?} did not complete within \ + {URING_DRAIN_MOUNT_ATTEMPTS} bounded attempts: a just-closed \ + fuse-over-io_uring connection wedges new mounts while the kernel \ + drains it (~2s). Retry shortly, avoid rapid unmount-then-mount \ + cycles, or set AGENTFS_FUSE_URING=0 on the mount-owning processes." + ), + )); + } + other => return other, + } } +} + +fn fuse_mount_fusermount_once( + mountpoint: &OsStr, + options: &[MountOption], + reply_timeout: Option, +) -> Result<(File, Option), Error> { + let fusermount_bin = detect_fusermount_bin(); let (child_socket, receive_socket) = UnixStream::pair()?; @@ -301,12 +338,24 @@ fn fuse_mount_fusermount( .arg(mountpoint) .env(FUSERMOUNT_COMM_ENV, child_socket.as_raw_fd().to_string()); - let fusermount_child = builder.spawn()?; + let mut fusermount_child = builder.spawn()?; drop(child_socket); // close socket in parent - let file = match receive_fusermount_message(&receive_socket) { + let file = match receive_fusermount_message(&receive_socket, reply_timeout) { Ok(f) => f, + Err(err) if err.kind() == ErrorKind::TimedOut => { + drop(receive_socket); + // The child is likely wedged inside mount(2); the kill is + // best-effort (an uninterruptible child survives it) and cannot + // be awaited without inheriting the wedge. The lazy unmount + // clears the mountpoint in case mount(2) actually completed + // after the deadline fired, so a retry does not stack mounts. + let _ = fusermount_child.kill(); + let _ = fusermount_child.try_wait(); + fuse_unmount_pure(&CString::new(mountpoint.as_bytes())?); + return Err(err); + } Err(_) => { // Drop receive socket, since fusermount has exited with an error drop(receive_socket); @@ -362,60 +411,7 @@ fn fuse_mount_fusermount( Ok((file, receive_socket)) } -// TODO: This method was written by Codex, and seems to work, but it would be good to audit it more thoroughly. -fn fuse_mount_mount_fusefs( - fusermount_bin: &str, - mountpoint: &OsStr, - options: &[MountOption], -) -> Result<(File, Option), Error> { - let fuse_device = OpenOptions::new() - .read(true) - .write(true) - .open("/dev/fuse")?; - - // Ensure the file descriptor is preserved across the helper exec. - let current_flags = unsafe { libc::fcntl(fuse_device.as_raw_fd(), libc::F_GETFD) }; - if current_flags == -1 { - return Err(io::Error::last_os_error()); - } - - if current_flags & libc::FD_CLOEXEC != 0 { - let cleared = current_flags & !libc::FD_CLOEXEC; - let ret = unsafe { libc::fcntl(fuse_device.as_raw_fd(), libc::F_SETFD, cleared) }; - if ret == -1 { - return Err(io::Error::last_os_error()); - } - } - - let mut builder = Command::new(fusermount_bin); - builder.stdout(Stdio::piped()).stderr(Stdio::piped()); - if !options.is_empty() { - builder.arg("-o"); - let options_strs: Vec = options.iter().map(option_to_string).collect(); - builder.arg(options_strs.join(",")); - } - - builder - .arg(fuse_device.as_raw_fd().to_string()) - .arg(mountpoint); - - let output = builder.output()?; - if !output.status.success() { - return Err(io::Error::new( - ErrorKind::Other, - String::from_utf8_lossy(&output.stderr).to_string(), - )); - } - - unsafe { - libc::fcntl(fuse_device.as_raw_fd(), libc::F_SETFD, libc::FD_CLOEXEC); - } - - Ok((fuse_device, None)) -} - // If returned option is none. Then fusermount binary should be tried -#[cfg(any(target_os = "linux", target_os = "macos"))] fn fuse_mount_sys(mountpoint: &OsStr, options: &[MountOption]) -> Result, Error> { let fuse_device_name = "/dev/fuse"; @@ -460,43 +456,13 @@ fn fuse_mount_sys(mountpoint: &OsStr, options: &[MountOption]) -> Result Result Result Result, Error> { - Ok(None) -} - -#[cfg(any(target_os = "linux", target_os = "macos"))] #[derive(PartialEq)] -pub enum MountOptionGroup { +pub(crate) enum MountOptionGroup { KernelOption, - KernelFlag, Fusermount, } -#[cfg(any(target_os = "linux", target_os = "macos"))] -pub fn option_group(option: &MountOption) -> MountOptionGroup { +pub(crate) fn option_group(option: &MountOption) -> MountOptionGroup { match option { MountOption::FSName(_) => MountOptionGroup::Fusermount, - MountOption::Subtype(_) => MountOptionGroup::Fusermount, - MountOption::CUSTOM(_) => MountOptionGroup::KernelOption, MountOption::AutoUnmount => MountOptionGroup::Fusermount, MountOption::AllowOther => MountOptionGroup::KernelOption, - MountOption::Dev => MountOptionGroup::KernelFlag, - MountOption::NoDev => MountOptionGroup::KernelFlag, - MountOption::Suid => MountOptionGroup::KernelFlag, - MountOption::NoSuid => MountOptionGroup::KernelFlag, - MountOption::RO => MountOptionGroup::KernelFlag, - MountOption::RW => MountOptionGroup::KernelFlag, - MountOption::Exec => MountOptionGroup::KernelFlag, - MountOption::NoExec => MountOptionGroup::KernelFlag, - MountOption::Atime => MountOptionGroup::KernelFlag, - MountOption::NoAtime => MountOptionGroup::KernelFlag, - MountOption::DirSync => MountOptionGroup::KernelFlag, - MountOption::Sync => MountOptionGroup::KernelFlag, - MountOption::Async => MountOptionGroup::KernelFlag, MountOption::AllowRoot => MountOptionGroup::KernelOption, MountOption::DefaultPermissions => MountOptionGroup::KernelOption, } } -#[cfg(target_os = "linux")] -pub fn option_to_flag(option: &MountOption) -> libc::c_ulong { - match option { - MountOption::Dev => 0, // There is no option for dev. It's the absence of NoDev - MountOption::NoDev => libc::MS_NODEV, - MountOption::Suid => 0, - MountOption::NoSuid => libc::MS_NOSUID, - MountOption::RW => 0, - MountOption::RO => libc::MS_RDONLY, - MountOption::Exec => 0, - MountOption::NoExec => libc::MS_NOEXEC, - MountOption::Atime => 0, - MountOption::NoAtime => libc::MS_NOATIME, - MountOption::Async => 0, - MountOption::Sync => libc::MS_SYNCHRONOUS, - MountOption::DirSync => libc::MS_DIRSYNC, - _ => unreachable!(), - } -} - -#[cfg(target_os = "macos")] -pub fn option_to_flag(option: &MountOption) -> libc::c_int { - match option { - MountOption::Dev => 0, // There is no option for dev. It's the absence of NoDev - MountOption::NoDev => BSD_MNT_NODEV, - MountOption::Suid => 0, - MountOption::NoSuid => libc::MNT_NOSUID, - MountOption::RW => 0, - MountOption::RO => libc::MNT_RDONLY, - MountOption::Exec => 0, - MountOption::NoExec => libc::MNT_NOEXEC, - MountOption::Atime => 0, - MountOption::NoAtime => libc::MNT_NOATIME, - MountOption::Async => 0, - MountOption::Sync => libc::MNT_SYNCHRONOUS, - _ => unreachable!(), - } -} - -#[cfg_attr( - any( - target_os = "freebsd", - target_os = "dragonfly", - target_os = "openbsd", - target_os = "netbsd" - ), - allow(dead_code) -)] -#[cfg(any( - target_os = "freebsd", - target_os = "dragonfly", - target_os = "openbsd", - target_os = "netbsd" -))] -pub fn option_to_flag(option: &MountOption) -> libc::c_int { - match option { - MountOption::Dev => 0, - MountOption::NoDev => BSD_MNT_NODEV, - MountOption::Suid => 0, - MountOption::NoSuid => libc::MNT_NOSUID, - MountOption::RW => 0, - MountOption::RO => libc::MNT_RDONLY, - MountOption::Exec => 0, - MountOption::NoExec => libc::MNT_NOEXEC, - MountOption::Atime => 0, - MountOption::NoAtime => libc::MNT_NOATIME, - MountOption::Async => 0, - MountOption::Sync => libc::MNT_SYNCHRONOUS, - MountOption::DirSync => libc::MNT_SYNCHRONOUS, - _ => unreachable!(), +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fusermount_reply_wait_times_out_when_nothing_arrives() { + let (_silent_peer, socket) = UnixStream::pair().unwrap(); + let start = Instant::now(); + let err = receive_fusermount_message(&socket, Some(Duration::from_millis(200))) + .expect_err("a silent fusermount peer must not block the mount forever"); + assert_eq!(err.kind(), ErrorKind::TimedOut); + assert!( + start.elapsed() < Duration::from_secs(5), + "the reply wait must be bounded, took {:?}", + start.elapsed() + ); + } + + #[test] + fn fusermount_reply_wait_surfaces_peer_exit_before_the_deadline() { + let (peer, socket) = UnixStream::pair().unwrap(); + drop(peer); + let start = Instant::now(); + let err = receive_fusermount_message(&socket, Some(Duration::from_secs(30))) + .expect_err("a dead fusermount peer must surface as an error"); + assert_eq!(err.kind(), ErrorKind::UnexpectedEof); + assert!( + start.elapsed() < Duration::from_secs(5), + "peer exit must not wait out the deadline, took {:?}", + start.elapsed() + ); } } diff --git a/cli/src/fuser/mnt/mod.rs b/crates/agentfs-fuse/src/transport/mnt/mod.rs similarity index 95% rename from cli/src/fuser/mnt/mod.rs rename to crates/agentfs-fuse/src/transport/mnt/mod.rs index ae2ddbd4..f7c64fae 100644 --- a/cli/src/fuser/mnt/mod.rs +++ b/crates/agentfs-fuse/src/transport/mnt/mod.rs @@ -4,12 +4,12 @@ //! This is a Linux-only pure-Rust implementation. mod fuse_pure; -pub mod mount_options; +pub(crate) mod mount_options; use std::fs::File; use std::io; -pub use fuse_pure::Mount; +pub(crate) use fuse_pure::Mount; use std::ffi::CStr; #[inline] diff --git a/crates/agentfs-fuse/src/transport/mnt/mount_options.rs b/crates/agentfs-fuse/src/transport/mnt/mount_options.rs new file mode 100644 index 00000000..5eff67fc --- /dev/null +++ b/crates/agentfs-fuse/src/transport/mnt/mount_options.rs @@ -0,0 +1,77 @@ +use std::io; +use std::io::ErrorKind; + +/// Mount options accepted by the FUSE filesystem type +/// See 'man mount.fuse' for details. +#[derive(Debug, Eq, PartialEq, Hash, Clone)] +pub(crate) enum MountOption { + /// Set the name of the source in mtab + FSName(String), + + /// Allow all users to access files on this filesystem. By default access is restricted to the + /// user who mounted it + AllowOther, + /// Allow the root user to access this filesystem, in addition to the user who mounted it + AllowRoot, + /// Automatically unmount when the mounting process exits + /// + /// `AutoUnmount` requires `AllowOther` or `AllowRoot`. If `AutoUnmount` is set and neither `Allow...` is set, the FUSE configuration must permit `allow_other`, otherwise mounting will fail. + AutoUnmount, + /// Enable permission checking in the kernel + DefaultPermissions, +} + +pub(crate) fn check_option_conflicts(options: &[MountOption]) -> Result<(), io::Error> { + if options.contains(&MountOption::AllowOther) && options.contains(&MountOption::AllowRoot) { + Err(io::Error::new( + ErrorKind::InvalidInput, + "Conflicting mount options found: AllowOther and AllowRoot", + )) + } else { + Ok(()) + } +} + +// Format option to be passed to libfuse or kernel +pub(crate) fn option_to_string(option: &MountOption) -> String { + match option { + MountOption::FSName(name) => format!("fsname={name}"), + MountOption::AutoUnmount => "auto_unmount".to_string(), + MountOption::AllowRoot | + // AllowRoot is implemented by allowing everyone access and then restricting to + // root + owner within fuser + MountOption::AllowOther => "allow_other".to_string(), + MountOption::DefaultPermissions => "default_permissions".to_string(), + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn option_checking() { + assert!( + check_option_conflicts(&[MountOption::AllowOther, MountOption::AllowRoot]).is_err() + ); + assert!(check_option_conflicts(&[ + MountOption::FSName("agentfs".to_owned()), + MountOption::DefaultPermissions, + ]) + .is_ok()); + } + + #[test] + fn option_strings() { + use super::MountOption::*; + + assert_eq!( + option_to_string(&FSName("agentfs".to_owned())), + "fsname=agentfs" + ); + assert_eq!(option_to_string(&AllowOther), "allow_other"); + assert_eq!(option_to_string(&AllowRoot), "allow_other"); + assert_eq!(option_to_string(&AutoUnmount), "auto_unmount"); + assert_eq!(option_to_string(&DefaultPermissions), "default_permissions"); + } +} diff --git a/cli/src/fuser/mod.rs b/crates/agentfs-fuse/src/transport/mod.rs similarity index 74% rename from cli/src/fuser/mod.rs rename to crates/agentfs-fuse/src/transport/mod.rs index 1d6b3883..10443bd3 100644 --- a/cli/src/fuser/mod.rs +++ b/crates/agentfs-fuse/src/transport/mod.rs @@ -7,59 +7,43 @@ #![allow( missing_docs, missing_debug_implementations, - dead_code, - unused_imports, - unexpected_cfgs, clippy::manual_is_multiple_of, clippy::io_other_error )] use libc::{c_int, ENOSYS, EPERM}; -use log::debug; -use mnt::mount_options::parse_options_from_args; use std::cmp::min; use std::ffi::OsStr; -use std::io; -use std::io::ErrorKind; use std::path::Path; use std::time::SystemTime; - -pub use ll::fuse_abi::consts; -pub use ll::fuse_abi::fuse_forget_one; -pub use ll::fuse_abi::FUSE_ROOT_ID; -pub use ll::TimeOrNow; -pub use mnt::mount_options::MountOption; -pub use notify::{Notifier, PollHandle}; -pub use reply::ReplyPoll; -pub use reply::ReplyXattr; -pub use reply::{Reply, ReplyAttr, ReplyData, ReplyEmpty, ReplyEntry, ReplyOpen}; -pub use reply::{ +use tracing::debug; + +pub(crate) use ll::fuse_abi::consts; +pub(crate) use ll::fuse_abi::fuse_forget_one; +pub(crate) use ll::TimeOrNow; +pub(crate) use mnt::mount_options::check_option_conflicts; +pub(crate) use mnt::mount_options::MountOption; +pub(crate) use reply::ReplyPoll; +pub(crate) use reply::ReplyXattr; +pub(crate) use reply::{ReplyAttr, ReplyData, ReplyEmpty, ReplyEntry, ReplyOpen}; +pub(crate) use reply::{ ReplyBmap, ReplyCreate, ReplyDirectory, ReplyDirectoryPlus, ReplyIoctl, ReplyLock, ReplyLseek, ReplyStatfs, ReplyWrite, }; -pub use request::Request; -pub use session::{BackgroundSession, Session, SessionACL, SessionUnmounter}; +pub(crate) use request::Request; +pub(crate) use session::{Session, SessionUnmounter}; use ll::fuse_abi::consts::*; -use mnt::mount_options::check_option_conflicts; use session::MAX_WRITE_SIZE; mod channel; -#[allow( - dead_code, - unused_imports, - unexpected_cfgs, - clippy::manual_is_multiple_of -)] pub(crate) mod deferred_notify; mod ll; #[allow(clippy::io_other_error)] mod mnt; #[allow(clippy::io_other_error)] mod notify; -#[allow(unexpected_cfgs)] mod reply; -#[allow(unused_imports, unexpected_cfgs)] mod request; mod session; #[cfg(target_os = "linux")] @@ -74,7 +58,7 @@ const fn default_init_flags(#[allow(unused_variables)] capabilities: u64) -> u64 /// File types #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum FileType { +pub(crate) enum FileType { /// Named pipe (`S_IFIFO`) NamedPipe, /// Character device (`S_IFCHR`) @@ -93,42 +77,42 @@ pub enum FileType { /// File attributes #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct FileAttr { +pub(crate) struct FileAttr { /// Inode number - pub ino: u64, + pub(crate) ino: u64, /// Size in bytes - pub size: u64, + pub(crate) size: u64, /// Size in blocks - pub blocks: u64, + pub(crate) blocks: u64, /// Time of last access - pub atime: SystemTime, + pub(crate) atime: SystemTime, /// Time of last modification - pub mtime: SystemTime, + pub(crate) mtime: SystemTime, /// Time of last change - pub ctime: SystemTime, - /// Time of creation (macOS only, but kept for API compatibility) - pub crtime: SystemTime, + pub(crate) ctime: SystemTime, + /// Time of creation retained for compatibility with the higher-level attribute type + pub(crate) crtime: SystemTime, /// Kind of file (directory, file, pipe, etc) - pub kind: FileType, + pub(crate) kind: FileType, /// Permissions - pub perm: u16, + pub(crate) perm: u16, /// Number of hard links - pub nlink: u32, + pub(crate) nlink: u32, /// User id - pub uid: u32, + pub(crate) uid: u32, /// Group id - pub gid: u32, + pub(crate) gid: u32, /// Rdev - pub rdev: u32, + pub(crate) rdev: u32, /// Block size - pub blksize: u32, - /// Flags (macOS only, but kept for API compatibility) - pub flags: u32, + pub(crate) blksize: u32, + /// Platform attribute flags retained for compatibility with the higher-level attribute type + pub(crate) flags: u32, } /// Configuration of the fuse kernel module connection #[derive(Debug)] -pub struct KernelConfig { +pub(crate) struct KernelConfig { capabilities: u64, requested: u64, max_readahead: u32, @@ -153,31 +137,8 @@ impl KernelConfig { } } - /// Set the timestamp granularity - pub fn set_time_granularity( - &mut self, - value: std::time::Duration, - ) -> Result { - if value.as_nanos() == 0 { - return Err(std::time::Duration::new(0, 1)); - } - if value.as_secs() > 1 || (value.as_secs() == 1 && value.subsec_nanos() > 0) { - return Err(std::time::Duration::new(1, 0)); - } - let mut power_of_10 = 1; - while power_of_10 < value.as_nanos() { - if value.as_nanos() < power_of_10 * 10 { - return Err(std::time::Duration::new(0, power_of_10 as u32)); - } - power_of_10 *= 10; - } - let previous = self.time_gran; - self.time_gran = value; - Ok(previous) - } - /// Set the maximum write size for a single request - pub fn set_max_write(&mut self, value: u32) -> Result { + pub(crate) fn set_max_write(&mut self, value: u32) -> Result { if value == 0 { return Err(1); } @@ -190,7 +151,7 @@ impl KernelConfig { } /// Set the maximum readahead size - pub fn set_max_readahead(&mut self, value: u32) -> Result { + pub(crate) fn set_max_readahead(&mut self, value: u32) -> Result { if value == 0 { return Err(1); } @@ -203,7 +164,7 @@ impl KernelConfig { } /// Add a set of capabilities - pub fn add_capabilities(&mut self, capabilities_to_add: u64) -> Result<(), u64> { + pub(crate) fn add_capabilities(&mut self, capabilities_to_add: u64) -> Result<(), u64> { if capabilities_to_add & self.capabilities != capabilities_to_add { return Err(capabilities_to_add - (capabilities_to_add & self.capabilities)); } @@ -211,26 +172,6 @@ impl KernelConfig { Ok(()) } - /// Set the maximum number of pending background requests - pub fn set_max_background(&mut self, value: u16) -> Result { - if value == 0 { - return Err(1); - } - let previous = self.max_background; - self.max_background = value; - Ok(previous) - } - - /// Set the congestion threshold - pub fn set_congestion_threshold(&mut self, value: u16) -> Result { - if value == 0 { - return Err(1); - } - let previous = self.congestion_threshold(); - self.congestion_threshold = Some(value); - Ok(previous) - } - pub(crate) fn congestion_threshold(&self) -> u16 { match self.congestion_threshold { None => (u32::from(self.max_background) * 3 / 4) as u16, @@ -242,26 +183,6 @@ impl KernelConfig { ((std::cmp::max(self.max_write, self.max_readahead) - 1) / page_size::get() as u32) as u16 + 1 } - - pub(crate) fn requested(&self) -> u64 { - self.requested - } - - pub(crate) fn max_readahead(&self) -> u32 { - self.max_readahead - } - - pub(crate) fn max_background(&self) -> u16 { - self.max_background - } - - pub(crate) fn max_write(&self) -> u32 { - self.max_write - } - - pub(crate) fn time_gran(&self) -> std::time::Duration { - self.time_gran - } } /// Filesystem trait. @@ -272,7 +193,7 @@ impl KernelConfig { /// Implementations MUST handle their own interior-mutability (e.g. `Mutex`, `RwLock`, /// atomics) for any mutable state they keep. #[allow(clippy::too_many_arguments)] -pub trait Filesystem: Send + Sync + 'static { +pub(crate) trait Filesystem: Send + Sync + 'static { /// Initialize filesystem. fn init(&self, _req: &Request, _config: &mut KernelConfig) -> Result<(), c_int> { Ok(()) @@ -316,15 +237,11 @@ pub trait Filesystem: Send + Sync + 'static { _mtime: Option, _ctime: Option, fh: Option, - _crtime: Option, - _chgtime: Option, - _bkuptime: Option, - flags: Option, reply: ReplyAttr, ) { debug!( "[Not Implemented] setattr(ino: {ino:#x?}, mode: {mode:?}, uid: {uid:?}, \ - gid: {gid:?}, size: {size:?}, fh: {fh:?}, flags: {flags:?})" + gid: {gid:?}, size: {size:?}, fh: {fh:?})" ); reply.error(ENOSYS); } @@ -667,14 +584,14 @@ pub trait Filesystem: Send + Sync + 'static { _req: &Request, ino: u64, fh: u64, - ph: PollHandle, + kernel_handle: u64, events: u32, flags: u32, reply: ReplyPoll, ) { debug!( "[Not Implemented] poll(ino: {ino:#x?}, fh: {fh}, \ - ph: {ph:?}, events: {events}, flags: {flags})" + kernel_handle: {kernel_handle:?}, events: {events}, flags: {flags})" ); reply.error(ENOSYS); } @@ -736,56 +653,3 @@ pub trait Filesystem: Send + Sync + 'static { reply.error(ENOSYS); } } - -/// Mount the given filesystem to the given mountpoint. This function will -/// not return until the filesystem is unmounted. -#[deprecated(note = "use mount2() instead")] -pub fn mount>( - filesystem: FS, - mountpoint: P, - options: &[&OsStr], -) -> io::Result<()> { - let options = parse_options_from_args(options)?; - mount2(filesystem, mountpoint, options.as_ref()) -} - -/// Mount the given filesystem to the given mountpoint. This function will -/// not return until the filesystem is unmounted. -pub fn mount2>( - filesystem: FS, - mountpoint: P, - options: &[MountOption], -) -> io::Result<()> { - check_option_conflicts(options)?; - Session::new(filesystem, mountpoint.as_ref(), options).and_then(|mut se| se.run()) -} - -/// Mount the given filesystem to the given mountpoint. This function spawns -/// a background thread to handle filesystem operations while being mounted -/// and therefore returns immediately. -#[deprecated(note = "use spawn_mount2() instead")] -pub fn spawn_mount<'a, FS: Filesystem + 'static + 'a, P: AsRef>( - filesystem: FS, - mountpoint: P, - options: &[&OsStr], -) -> io::Result { - let options: Option> = options - .iter() - .map(|x| Some(MountOption::from_str(x.to_str()?))) - .collect(); - let options = options.ok_or(ErrorKind::InvalidData)?; - Session::new(filesystem, mountpoint.as_ref(), options.as_ref()) - .and_then(session::Session::spawn) -} - -/// Mount the given filesystem to the given mountpoint. This function spawns -/// a background thread to handle filesystem operations while being mounted -/// and therefore returns immediately. -pub fn spawn_mount2<'a, FS: Filesystem + 'static + 'a, P: AsRef>( - filesystem: FS, - mountpoint: P, - options: &[MountOption], -) -> io::Result { - check_option_conflicts(options)?; - Session::new(filesystem, mountpoint.as_ref(), options).and_then(session::Session::spawn) -} diff --git a/crates/agentfs-fuse/src/transport/notify.rs b/crates/agentfs-fuse/src/transport/notify.rs new file mode 100644 index 00000000..bc982fc9 --- /dev/null +++ b/crates/agentfs-fuse/src/transport/notify.rs @@ -0,0 +1,63 @@ +use std::ffi::OsStr; +use std::io; + +use super::{ + channel::ChannelSender, + ll::{fuse_abi::fuse_notify_code as notify_code, notify::Notification}, + + // What we're sending here aren't really replies, but they + // move in the same direction (userspace->kernel), so we can + // reuse ReplySender for it. + reply::ReplySender, +}; + +/// A handle by which the application can send notifications to the server +#[derive(Debug, Clone)] +pub(crate) struct Notifier(ChannelSender); + +impl Notifier { + pub(crate) fn new(cs: ChannelSender) -> Self { + Self(cs) + } + + /// Invalidate the kernel cache for a given directory entry + /// # Errors + /// Returns an error if the notification data is too large. + /// Returns an error if the kernel rejects the notification. + pub(crate) fn inval_entry(&self, parent: u64, name: &OsStr) -> io::Result<()> { + let notif = Notification::new_inval_entry(parent, name).map_err(Self::too_big_err)?; + self.send_inval(notify_code::FUSE_NOTIFY_INVAL_ENTRY, ¬if) + } + + /// Invalidate the kernel cache for a given inode (metadata and + /// data in the given range) + /// # Errors + /// Returns an error if the kernel rejects the notification. + pub(crate) fn inval_inode(&self, ino: u64, offset: i64, len: i64) -> io::Result<()> { + let notif = Notification::new_inval_inode(ino, offset, len); + self.send_inval(notify_code::FUSE_NOTIFY_INVAL_INODE, ¬if) + } + + fn send_inval(&self, code: notify_code, notification: &Notification<'_>) -> io::Result<()> { + match self.send(code, notification) { + // ENOENT is harmless for an invalidation (the + // kernel may have already dropped the cached + // entry on its own anyway), so ignore it. + Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()), + x => x, + } + } + + fn send(&self, code: notify_code, notification: &Notification<'_>) -> io::Result<()> { + notification + .with_iovec(code, |iov| self.0.send(iov)) + .map_err(Self::too_big_err)? + } + + /// Create an error for indicating when a notification message + /// would exceed the capacity that its length descriptor field is + /// capable of encoding. + fn too_big_err(tfie: std::num::TryFromIntError) -> io::Error { + io::Error::new(io::ErrorKind::Other, format!("Data too large: {tfie:?}")) + } +} diff --git a/cli/src/fuser/reply.rs b/crates/agentfs-fuse/src/transport/reply.rs similarity index 67% rename from cli/src/fuser/reply.rs rename to crates/agentfs-fuse/src/transport/reply.rs index 582d7aaa..9505c72b 100644 --- a/cli/src/fuser/reply.rs +++ b/crates/agentfs-fuse/src/transport/reply.rs @@ -16,17 +16,17 @@ use super::ll::{ INodeNo, }; use libc::c_int; -use log::{error, warn}; use std::convert::AsRef; use std::ffi::OsStr; use std::fmt; use std::io::IoSlice; use std::time::Duration; +use tracing::{error, warn}; use super::{FileAttr, FileType}; /// Generic reply callback to send data -pub trait ReplySender: Send + Sync + Unpin + 'static { +pub(crate) trait ReplySender: Send + Sync + Unpin + 'static { /// Send data. fn send(&self, data: &[IoSlice<'_>]) -> std::io::Result<()>; } @@ -38,7 +38,7 @@ impl fmt::Debug for Box { } /// Generic reply trait -pub trait Reply { +pub(crate) trait Reply { /// Create a new reply for the given request fn new(unique: u64, sender: S) -> Self; } @@ -80,7 +80,7 @@ impl ReplyRaw { } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { assert_ne!(err, 0); self.send_ll(&ll::Response::new_error(ll::Errno::from_i32(err))); } @@ -102,7 +102,7 @@ impl Drop for ReplyRaw { /// Empty reply /// #[derive(Debug)] -pub struct ReplyEmpty { +pub(crate) struct ReplyEmpty { reply: ReplyRaw, } @@ -116,12 +116,16 @@ impl Reply for ReplyEmpty { impl ReplyEmpty { /// Reply to a request with nothing - pub fn ok(self) { + pub(crate) fn ok(self) { self.reply.send_ll(&ll::Response::new_empty()); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + #[allow( + dead_code, + reason = "AgentFS statfs path currently replies only success" + )] + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -130,7 +134,7 @@ impl ReplyEmpty { /// Data reply /// #[derive(Debug)] -pub struct ReplyData { +pub(crate) struct ReplyData { reply: ReplyRaw, } @@ -144,12 +148,16 @@ impl Reply for ReplyData { impl ReplyData { /// Reply to a request with the given data - pub fn data(self, data: &[u8]) { + pub(crate) fn data(self, data: &[u8]) { self.reply.send_ll(&ll::Response::new_slice(data)); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + #[allow( + dead_code, + reason = "AgentFS statfs path currently replies only success" + )] + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -158,7 +166,7 @@ impl ReplyData { /// Entry reply /// #[derive(Debug)] -pub struct ReplyEntry { +pub(crate) struct ReplyEntry { reply: ReplyRaw, } @@ -172,12 +180,13 @@ impl Reply for ReplyEntry { impl ReplyEntry { /// Reply to a request with the given entry - pub fn entry(self, ttl: &Duration, attr: &FileAttr, generation: u64) { + #[allow(dead_code, reason = "adapter uses separate TTL entry replies")] + pub(crate) fn entry(self, ttl: &Duration, attr: &FileAttr, generation: u64) { self.entry_with_ttls(ttl, ttl, attr, generation); } /// Reply to a request with the given entry and separate entry/attribute TTLs. - pub fn entry_with_ttls( + pub(crate) fn entry_with_ttls( self, entry_ttl: &Duration, attr_ttl: &Duration, @@ -194,12 +203,16 @@ impl ReplyEntry { } /// Reply to a lookup with a cacheable negative entry. - pub fn negative(self, ttl: &Duration) { + pub(crate) fn negative(self, ttl: &Duration) { self.reply.send_ll(&ll::Response::new_negative_entry(*ttl)); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + #[allow( + dead_code, + reason = "AgentFS statfs path currently replies only success" + )] + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -208,7 +221,7 @@ impl ReplyEntry { /// Attribute Reply /// #[derive(Debug)] -pub struct ReplyAttr { +pub(crate) struct ReplyAttr { reply: ReplyRaw, } @@ -222,45 +235,13 @@ impl Reply for ReplyAttr { impl ReplyAttr { /// Reply to a request with the given attribute - pub fn attr(self, ttl: &Duration, attr: &FileAttr) { + pub(crate) fn attr(self, ttl: &Duration, attr: &FileAttr) { self.reply .send_ll(&ll::Response::new_attr(ttl, &attr.into())); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// XTimes Reply -/// -#[cfg(target_os = "macos")] -#[derive(Debug)] -pub struct ReplyXTimes { - reply: ReplyRaw, -} - -#[cfg(target_os = "macos")] -impl Reply for ReplyXTimes { - fn new(unique: u64, sender: S) -> ReplyXTimes { - ReplyXTimes { - reply: Reply::new(unique, sender), - } - } -} - -#[cfg(target_os = "macos")] -impl ReplyXTimes { - /// Reply to a request with the given xtimes - pub fn xtimes(self, bkuptime: SystemTime, crtime: SystemTime) { - self.reply - .send_ll(&ll::Response::new_xtimes(bkuptime, crtime)) - } - - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -269,7 +250,7 @@ impl ReplyXTimes { /// Open Reply /// #[derive(Debug)] -pub struct ReplyOpen { +pub(crate) struct ReplyOpen { reply: ReplyRaw, } @@ -283,37 +264,13 @@ impl Reply for ReplyOpen { impl ReplyOpen { /// Reply to a request with the given open result - /// # Panics - /// When attempting to use kernel passthrough. Use `opened_passthrough()` instead. - pub fn opened(self, fh: u64, flags: u32) { - #[cfg(feature = "abi-7-40")] - assert_eq!(flags & FOPEN_PASSTHROUGH, 0); + pub(crate) fn opened(self, fh: u64, flags: u32) { self.reply - .send_ll(&ll::Response::new_open(ll::FileHandle(fh), flags, 0)); - } - - /// Registers a fd for passthrough, returning a `BackingId`. Once you have the backing ID, - /// you can pass it as the 3rd parameter of `OpenReply::opened_passthrough()`. This is done in - /// two separate steps because it may make sense to reuse backing IDs (to avoid having to - /// repeatedly reopen the underlying file or potentially keep thousands of fds open). - #[cfg(feature = "abi-7-40")] - pub fn open_backing(&self, fd: impl std::os::fd::AsFd) -> std::io::Result { - self.reply.sender.as_ref().unwrap().open_backing(fd.as_fd()) - } - - /// Reply to a request with an opened backing id. Call `ReplyOpen::open_backing()` to get one of - /// these. - #[cfg(feature = "abi-7-40")] - pub fn opened_passthrough(self, fh: u64, flags: u32, backing_id: &BackingId) { - self.reply.send_ll(&ll::Response::new_open( - ll::FileHandle(fh), - flags | FOPEN_PASSTHROUGH, - backing_id.backing_id, - )); + .send_ll(&ll::Response::new_open(ll::FileHandle(fh), flags)); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -322,7 +279,7 @@ impl ReplyOpen { /// Write Reply /// #[derive(Debug)] -pub struct ReplyWrite { +pub(crate) struct ReplyWrite { reply: ReplyRaw, } @@ -336,12 +293,12 @@ impl Reply for ReplyWrite { impl ReplyWrite { /// Reply to a request with the number of bytes written - pub fn written(self, size: u32) { + pub(crate) fn written(self, size: u32) { self.reply.send_ll(&ll::Response::new_write(size)); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -350,7 +307,7 @@ impl ReplyWrite { /// Statfs Reply /// #[derive(Debug)] -pub struct ReplyStatfs { +pub(crate) struct ReplyStatfs { reply: ReplyRaw, } @@ -365,7 +322,7 @@ impl Reply for ReplyStatfs { impl ReplyStatfs { /// Reply to a statfs request with filesystem information #[allow(clippy::too_many_arguments)] - pub fn statfs( + pub(crate) fn statfs( self, blocks: u64, bfree: u64, @@ -382,7 +339,11 @@ impl ReplyStatfs { } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + #[allow( + dead_code, + reason = "AgentFS statfs path currently replies only success" + )] + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -391,7 +352,7 @@ impl ReplyStatfs { /// Create reply /// #[derive(Debug)] -pub struct ReplyCreate { +pub(crate) struct ReplyCreate { reply: ReplyRaw, } @@ -407,14 +368,22 @@ impl ReplyCreate { /// Reply to a request with a newly created file entry and its newly open file handle /// # Panics /// When attempting to use kernel passthrough. Use `opened_passthrough()` instead. - pub fn created(self, ttl: &Duration, attr: &FileAttr, generation: u64, fh: u64, flags: u32) { + #[allow(dead_code, reason = "adapter uses separate TTL create replies")] + pub(crate) fn created( + self, + ttl: &Duration, + attr: &FileAttr, + generation: u64, + fh: u64, + flags: u32, + ) { self.created_with_ttls(ttl, ttl, attr, generation, fh, flags); } /// Reply to a request with a newly created file entry and separate entry/attribute TTLs. /// # Panics /// When attempting to use kernel passthrough. Use `opened_passthrough()` instead. - pub fn created_with_ttls( + pub(crate) fn created_with_ttls( self, entry_ttl: &Duration, attr_ttl: &Duration, @@ -423,8 +392,6 @@ impl ReplyCreate { fh: u64, flags: u32, ) { - #[cfg(feature = "abi-7-40")] - assert_eq!(flags & FOPEN_PASSTHROUGH, 0); self.reply.send_ll(&ll::Response::new_create( attr_ttl, entry_ttl, @@ -432,12 +399,11 @@ impl ReplyCreate { ll::Generation(generation), ll::FileHandle(fh), flags, - 0, )); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -446,7 +412,7 @@ impl ReplyCreate { /// Lock Reply /// #[derive(Debug)] -pub struct ReplyLock { +pub(crate) struct ReplyLock { reply: ReplyRaw, } @@ -460,7 +426,11 @@ impl Reply for ReplyLock { impl ReplyLock { /// Reply to a request with a file lock - pub fn locked(self, start: u64, end: u64, typ: i32, pid: u32) { + #[allow( + dead_code, + reason = "success reply retained for unsupported lock operation" + )] + pub(crate) fn locked(self, start: u64, end: u64, typ: i32, pid: u32) { self.reply.send_ll(&ll::Response::new_lock(&ll::Lock { range: (start, end), typ, @@ -469,7 +439,7 @@ impl ReplyLock { } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -478,7 +448,7 @@ impl ReplyLock { /// Bmap Reply /// #[derive(Debug)] -pub struct ReplyBmap { +pub(crate) struct ReplyBmap { reply: ReplyRaw, } @@ -492,12 +462,16 @@ impl Reply for ReplyBmap { impl ReplyBmap { /// Reply to a request with a bmap - pub fn bmap(self, block: u64) { + #[allow( + dead_code, + reason = "success reply retained for unsupported bmap operation" + )] + pub(crate) fn bmap(self, block: u64) { self.reply.send_ll(&ll::Response::new_bmap(block)); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -506,7 +480,7 @@ impl ReplyBmap { /// Ioctl Reply /// #[derive(Debug)] -pub struct ReplyIoctl { +pub(crate) struct ReplyIoctl { reply: ReplyRaw, } @@ -520,13 +494,17 @@ impl Reply for ReplyIoctl { impl ReplyIoctl { /// Reply to a request with an ioctl - pub fn ioctl(self, result: i32, data: &[u8]) { + #[allow( + dead_code, + reason = "success reply retained for unsupported ioctl operation" + )] + pub(crate) fn ioctl(self, result: i32, data: &[u8]) { self.reply .send_ll(&ll::Response::new_ioctl(result, &[IoSlice::new(data)])); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -535,7 +513,7 @@ impl ReplyIoctl { /// Poll Reply /// #[derive(Debug)] -pub struct ReplyPoll { +pub(crate) struct ReplyPoll { reply: ReplyRaw, } @@ -549,12 +527,16 @@ impl Reply for ReplyPoll { impl ReplyPoll { /// Reply to a request with ready poll events - pub fn poll(self, revents: u32) { + #[allow( + dead_code, + reason = "success reply retained for unsupported poll operation" + )] + pub(crate) fn poll(self, revents: u32) { self.reply.send_ll(&ll::Response::new_poll(revents)); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -563,14 +545,14 @@ impl ReplyPoll { /// Directory reply /// #[derive(Debug)] -pub struct ReplyDirectory { +pub(crate) struct ReplyDirectory { reply: ReplyRaw, data: DirEntList, } impl ReplyDirectory { /// Creates a new `ReplyDirectory` with a specified buffer size. - pub fn new(unique: u64, sender: S, size: usize) -> ReplyDirectory { + pub(crate) fn new(unique: u64, sender: S, size: usize) -> ReplyDirectory { ReplyDirectory { reply: Reply::new(unique, sender), data: DirEntList::new(size), @@ -581,7 +563,17 @@ impl ReplyDirectory { /// A transparent offset value can be provided for each entry. The kernel uses these /// value to request the next entries in further readdir calls #[must_use] - pub fn add>(&mut self, ino: u64, offset: i64, kind: FileType, name: T) -> bool { + #[allow( + dead_code, + reason = "READDIRPLUS path is the active directory reply path" + )] + pub(crate) fn add>( + &mut self, + ino: u64, + offset: i64, + kind: FileType, + name: T, + ) -> bool { let name = name.as_ref(); self.data.push(&DirEntry::new( INodeNo(ino), @@ -592,12 +584,12 @@ impl ReplyDirectory { } /// Reply to a request with the filled directory buffer - pub fn ok(self) { + pub(crate) fn ok(self) { self.reply.send_ll(&self.data.into()); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -606,14 +598,14 @@ impl ReplyDirectory { /// `DirectoryPlus` reply /// #[derive(Debug)] -pub struct ReplyDirectoryPlus { +pub(crate) struct ReplyDirectoryPlus { reply: ReplyRaw, buf: DirEntPlusList, } impl ReplyDirectoryPlus { /// Creates a new `ReplyDirectory` with a specified buffer size. - pub fn new(unique: u64, sender: S, size: usize) -> ReplyDirectoryPlus { + pub(crate) fn new(unique: u64, sender: S, size: usize) -> ReplyDirectoryPlus { ReplyDirectoryPlus { reply: Reply::new(unique, sender), buf: DirEntPlusList::new(size), @@ -623,7 +615,8 @@ impl ReplyDirectoryPlus { /// Add an entry to the directory reply buffer. Returns true if the buffer is full. /// A transparent offset value can be provided for each entry. The kernel uses these /// value to request the next entries in further readdir calls - pub fn add>( + #[allow(dead_code, reason = "adapter uses separate TTL readdirplus entries")] + pub(crate) fn add>( &mut self, ino: u64, offset: i64, @@ -637,7 +630,7 @@ impl ReplyDirectoryPlus { /// Add an entry to the directory-plus reply buffer with separate entry/attribute TTLs. #[allow(clippy::too_many_arguments)] - pub fn add_with_ttls>( + pub(crate) fn add_with_ttls>( &mut self, ino: u64, offset: i64, @@ -660,12 +653,12 @@ impl ReplyDirectoryPlus { } /// Reply to a request with the filled directory buffer - pub fn ok(self) { + pub(crate) fn ok(self) { self.reply.send_ll(&self.buf.into()); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -674,7 +667,7 @@ impl ReplyDirectoryPlus { /// Xattr reply /// #[derive(Debug)] -pub struct ReplyXattr { +pub(crate) struct ReplyXattr { reply: ReplyRaw, } @@ -688,17 +681,25 @@ impl Reply for ReplyXattr { impl ReplyXattr { /// Reply to a request with the size of an extended attribute - pub fn size(self, size: u32) { + #[allow( + dead_code, + reason = "success reply retained for unsupported xattr operation" + )] + pub(crate) fn size(self, size: u32) { self.reply.send_ll(&ll::Response::new_xattr_size(size)); } /// Reply to a request with the data of an extended attribute - pub fn data(self, data: &[u8]) { + #[allow( + dead_code, + reason = "success reply retained for unsupported xattr operation" + )] + pub(crate) fn data(self, data: &[u8]) { self.reply.send_ll(&ll::Response::new_slice(data)); } /// Reply to a request with the given error code. - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -707,7 +708,7 @@ impl ReplyXattr { /// Lseek Reply /// #[derive(Debug)] -pub struct ReplyLseek { +pub(crate) struct ReplyLseek { reply: ReplyRaw, } @@ -721,12 +722,16 @@ impl Reply for ReplyLseek { impl ReplyLseek { /// Reply to a request with seeked offset - pub fn offset(self, offset: i64) { + #[allow( + dead_code, + reason = "success reply retained for unsupported lseek operation" + )] + pub(crate) fn offset(self, offset: i64) { self.reply.send_ll(&ll::Response::new_lseek(offset)); } /// Reply to a request with the given error code - pub fn error(self, err: c_int) { + pub(crate) fn error(self, err: c_int) { self.reply.error(err); } } @@ -782,11 +787,6 @@ mod test { assert_eq!(self.expected, v); Ok(()) } - - #[cfg(feature = "abi-7-40")] - fn open_backing(&self, _fd: BorrowedFd<'_>) -> std::io::Result { - unreachable!() - } } #[test] @@ -844,34 +844,18 @@ mod test { #[test] fn reply_entry() { - let mut expected = if cfg!(target_os = "macos") { - vec![ - 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, - 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, - 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, - ] - } else { - vec![ - 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, - 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, - ] - }; + let mut expected = vec![ + 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, + 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, + 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, + 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + ]; expected.extend(vec![0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); expected[0] = (expected.len()) as u8; @@ -902,31 +886,16 @@ mod test { #[test] fn reply_attr() { - let mut expected = if cfg!(target_os = "macos") { - vec![ - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, - 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, - 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, - 0x00, 0x00, - ] - } else { - vec![ - 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, - 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, - ] - }; + let mut expected = vec![ + 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, + 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, + 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, + 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + ]; expected.extend_from_slice(&[0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); expected[0] = expected.len() as u8; @@ -955,21 +924,6 @@ mod test { reply.attr(&ttl, &attr); } - #[test] - #[cfg(target_os = "macos")] - fn reply_xtimes() { - let sender = AssertSender { - expected: vec![ - 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, - ], - }; - let reply: ReplyXTimes = Reply::new(0xdeadbeef, sender); - let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); - reply.xtimes(time, time); - } - #[test] fn reply_open() { let sender = AssertSender { @@ -1014,36 +968,19 @@ mod test { #[test] fn reply_create() { - let mut expected = if cfg!(target_os = "macos") { - vec![ - 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, - 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, - 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0xbb, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ] - } else { - vec![ - 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, - 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ] - }; + let mut expected = vec![ + 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, + 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, + 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, + 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; let insert_at = expected.len() - 16; expected.splice( @@ -1148,11 +1085,6 @@ mod test { self.send(()).unwrap(); Ok(()) } - - #[cfg(feature = "abi-7-40")] - fn open_backing(&self, _fd: BorrowedFd<'_>) -> std::io::Result { - unreachable!() - } } #[test] diff --git a/cli/src/fuser/request.rs b/crates/agentfs-fuse/src/transport/request.rs similarity index 83% rename from cli/src/fuser/request.rs rename to crates/agentfs-fuse/src/transport/request.rs index 6ffb80d4..2cd8597c 100644 --- a/cli/src/fuser/request.rs +++ b/crates/agentfs-fuse/src/transport/request.rs @@ -7,11 +7,11 @@ //! on demand from it. use super::ll::{fuse_abi as abi, Errno, Response}; -use log::{debug, error, warn}; use std::convert::TryFrom; use std::convert::TryInto; use std::path::Path; use std::sync::Arc; +use tracing::{debug, error, warn}; use super::channel::ChannelSender; use super::deferred_notify::DeferredNotifier; @@ -21,13 +21,12 @@ use super::reply::ReplyDirectoryPlus; use super::reply::{Reply, ReplyDirectory, ReplySender}; use super::session::{SessionACL, SessionShared}; use super::Filesystem; -use super::PollHandle; use super::{ll, KernelConfig}; /// Classify a parsed request into a per-op latency slot (see -/// `agentfs_sdk::profiling::FuseOpSlot`). -fn fuse_op_slot(op: &ll::Operation<'_>) -> agentfs_sdk::profiling::FuseOpSlot { - use agentfs_sdk::profiling::FuseOpSlot as Slot; +/// `crate::telemetry::FuseOpSlot`). +fn fuse_op_slot(op: &ll::Operation<'_>) -> crate::telemetry::FuseOpSlot { + use crate::telemetry::FuseOpSlot as Slot; match op { ll::Operation::Lookup(_) => Slot::Lookup, ll::Operation::GetAttr(_) => Slot::GetAttr, @@ -112,7 +111,7 @@ impl std::fmt::Debug for AlignedRequestBuf { /// reading the next kernel message. Parsing of `ll::AnyRequest` happens on demand inside /// `dispatch`, borrowing from the owned buffer for the duration of the call. #[derive(Debug)] -pub struct Request { +pub(crate) struct Request { /// Channel sender for sending the reply ch: ChannelSender, /// Deferred notifier for enqueueing cache invalidations @@ -123,9 +122,6 @@ pub struct Request { #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] pub(crate) enum ScheduleKey { - FileHandle(u64), - Inode(u64), - Parent(u64), Pid(u64), } @@ -151,7 +147,7 @@ impl Request { } /// Returns the deferred-cache-invalidation handle tied to this session. - pub fn deferred_notifier(&self) -> &DeferredNotifier { + pub(crate) fn deferred_notifier(&self) -> &DeferredNotifier { &self.deferred } @@ -160,7 +156,7 @@ impl Request { .expect("header validated at construction time") } - pub fn notifier(&self) -> Notifier { + pub(crate) fn notifier(&self) -> Notifier { Notifier::new(self.ch.for_notify()) } @@ -175,39 +171,23 @@ impl Request { ll::Operation::Init(_) | ll::Operation::Destroy(_) | ll::Operation::BatchForget(_) - | ll::Operation::Interrupt(_) - | ll::Operation::NotifyReply(_) - | ll::Operation::CuseInit(_) => ScheduleClass::GlobalWrite, - #[cfg(target_os = "macos")] - ll::Operation::SetVolName(_) => ScheduleClass::GlobalWrite, + | ll::Operation::Interrupt(_) => ScheduleClass::GlobalWrite, _ => ScheduleClass::Keyed(pid_key), } } - /// Returns the unique identifier of this request - #[inline] - pub fn unique(&self) -> u64 { - self.request().unique().into() - } - /// Returns the uid of this request #[inline] - pub fn uid(&self) -> u32 { + pub(crate) fn uid(&self) -> u32 { self.request().uid() } /// Returns the gid of this request #[inline] - pub fn gid(&self) -> u32 { + pub(crate) fn gid(&self) -> u32 { self.request().gid() } - /// Returns the pid of this request - #[inline] - pub fn pid(&self) -> u32 { - self.request().pid() - } - /// Dispatch request to the given filesystem. This calls the appropriate filesystem /// operation method for the request and sends back the returned reply to the kernel. /// @@ -217,23 +197,15 @@ impl Request { let parsed = self.request(); debug!("{}", parsed); let unique = parsed.unique(); - let started = std::time::Instant::now(); let op_slot = parsed.operation().ok().map(|op| fuse_op_slot(&op)); + let _op_timer = op_slot.map(crate::telemetry::fuse_op_timer); let res = match self.dispatch_req(shared, &parsed) { Ok(Some(resp)) => resp, - Ok(None) => { - if let Some(slot) = op_slot { - agentfs_sdk::profiling::record_fuse_op(slot, started.elapsed()); - } - return; - } + Ok(None) => return, Err(errno) => parsed.reply_err(errno), } .with_iovec(unique, |iov| self.ch.send(iov)); - if let Some(slot) = op_slot { - agentfs_sdk::profiling::record_fuse_op(slot, started.elapsed()); - } if let Err(err) = res { warn!("Request {unique:?}: Failed to send reply: {err}"); @@ -252,45 +224,22 @@ impl Request { && parsed.uid() != 0) || (shared.allowed == SessionACL::Owner && parsed.uid() != shared.session_owner) { - #[cfg(feature = "abi-7-21")] - { - match op { - // Only allow operations that the kernel may issue without a uid set - ll::Operation::Init(_) - | ll::Operation::Destroy(_) - | ll::Operation::Read(_) - | ll::Operation::ReadDir(_) - | ll::Operation::ReadDirPlus(_) - | ll::Operation::BatchForget(_) - | ll::Operation::Forget(_) - | ll::Operation::Write(_) - | ll::Operation::FSync(_) - | ll::Operation::FSyncDir(_) - | ll::Operation::Release(_) - | ll::Operation::ReleaseDir(_) => {} - _ => { - return Err(Errno::EACCES); - } - } - } - #[cfg(not(feature = "abi-7-21"))] - { - match op { - // Only allow operations that the kernel may issue without a uid set - ll::Operation::Init(_) - | ll::Operation::Destroy(_) - | ll::Operation::Read(_) - | ll::Operation::ReadDir(_) - | ll::Operation::BatchForget(_) - | ll::Operation::Forget(_) - | ll::Operation::Write(_) - | ll::Operation::FSync(_) - | ll::Operation::FSyncDir(_) - | ll::Operation::Release(_) - | ll::Operation::ReleaseDir(_) => {} - _ => { - return Err(Errno::EACCES); - } + match op { + // Only allow operations that the kernel may issue without a uid set + ll::Operation::Init(_) + | ll::Operation::Destroy(_) + | ll::Operation::Read(_) + | ll::Operation::ReadDir(_) + | ll::Operation::ReadDirPlus(_) + | ll::Operation::BatchForget(_) + | ll::Operation::Forget(_) + | ll::Operation::Write(_) + | ll::Operation::FSync(_) + | ll::Operation::FSyncDir(_) + | ll::Operation::Release(_) + | ll::Operation::ReleaseDir(_) => {} + _ => { + return Err(Errno::EACCES); } } } @@ -382,14 +331,10 @@ impl Request { x.mtime(), x.ctime(), x.file_handle().map(std::convert::Into::into), - x.crtime(), - x.chgtime(), - x.bkuptime(), - x.flags(), self.reply(), ); } - ll::Operation::ReadLink(_) => { + ll::Operation::ReadLink => { shared .filesystem .readlink(self, parsed.nodeid().into(), self.reply()); @@ -551,7 +496,7 @@ impl Request { self.reply(), ); } - ll::Operation::StatFs(_) => { + ll::Operation::StatFs => { shared .filesystem .statfs(self, parsed.nodeid().into(), self.reply()); @@ -669,26 +614,19 @@ impl Request { ); } ll::Operation::Poll(x) => { - let ph = PollHandle::new(self.ch.for_notify(), x.kernel_handle()); - shared.filesystem.poll( self, parsed.nodeid().into(), x.file_handle().into(), - ph, + x.kernel_handle(), x.events(), x.flags(), self.reply(), ); } - ll::Operation::NotifyReply(_) => { - // TODO: handle FUSE_NOTIFY_REPLY - return Err(Errno::ENOSYS); - } ll::Operation::BatchForget(x) => { shared.filesystem.batch_forget(self, x.nodes()); // no reply } - #[cfg(feature = "abi-7-19")] ll::Operation::FAllocate(x) => { shared.filesystem.fallocate( self, @@ -700,7 +638,6 @@ impl Request { self.reply(), ); } - #[cfg(feature = "abi-7-21")] ll::Operation::ReadDirPlus(x) => { shared.filesystem.readdirplus( self, @@ -714,7 +651,6 @@ impl Request { ), ); } - #[cfg(feature = "abi-7-23")] ll::Operation::Rename2(x) => { shared.filesystem.rename( self, @@ -726,7 +662,6 @@ impl Request { self.reply(), ); } - #[cfg(feature = "abi-7-24")] ll::Operation::Lseek(x) => { shared.filesystem.lseek( self, @@ -737,7 +672,6 @@ impl Request { self.reply(), ); } - #[cfg(feature = "abi-7-28")] ll::Operation::CopyFileRange(x) => { let (i, o) = (x.src(), x.dest()); shared.filesystem.copy_file_range( @@ -753,33 +687,6 @@ impl Request { self.reply(), ); } - #[cfg(target_os = "macos")] - ll::Operation::SetVolName(x) => { - shared.filesystem.setvolname(self, x.name(), self.reply()); - } - #[cfg(target_os = "macos")] - ll::Operation::GetXTimes(x) => { - shared - .filesystem - .getxtimes(self, x.nodeid().into(), self.reply()); - } - #[cfg(target_os = "macos")] - ll::Operation::Exchange(x) => { - shared.filesystem.exchange( - self, - x.from().dir.into(), - x.from().name.as_ref(), - x.to().dir.into(), - x.to().name.as_ref(), - x.options(), - self.reply(), - ); - } - - ll::Operation::CuseInit(_) => { - // TODO: handle CUSE_INIT - return Err(Errno::ENOSYS); - } } Ok(None) } diff --git a/cli/src/fuser/session.rs b/crates/agentfs-fuse/src/transport/session.rs similarity index 62% rename from cli/src/fuser/session.rs rename to crates/agentfs-fuse/src/transport/session.rs index e32c092c..066c3dc3 100644 --- a/cli/src/fuser/session.rs +++ b/crates/agentfs-fuse/src/transport/session.rs @@ -6,42 +6,43 @@ //! for filesystem operations under its mount point. use libc::{EAGAIN, EINTR, ENODEV, ENOENT}; -use log::{debug, warn}; use nix::unistd::geteuid; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::io; -use std::os::fd::{AsFd, BorrowedFd, OwnedFd}; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd}; use std::path::{Path, PathBuf}; use std::sync::{ atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}, Arc, Mutex, }; -use std::thread::{self, JoinHandle}; +use std::thread; use std::time::Instant; +use tracing::{debug, warn}; + +use crate::adapter::config::{DispatchMode, UringConfig, FUSE_REQUEST_BUFFER_SIZE}; use std::sync::mpsc; use super::deferred_notify::{DeferredNotifier, NotifyOp}; use super::ll::fuse_abi as abi; -use super::request::{AlignedRequestBuf, Request, ScheduleClass, ScheduleKey}; +use super::notify::Notifier; +use super::request::{AlignedRequestBuf, Request, ScheduleClass}; use super::Filesystem; use super::MountOption; use super::{channel::Channel, mnt::Mount}; -use super::{channel::ChannelSender, notify::Notifier}; /// The max size of write requests from the kernel. The absolute minimum is 4k, -/// FUSE recommends at least 128k, max 16M. The FUSE default is 16M on macOS -/// and 128k on other systems. -pub const MAX_WRITE_SIZE: usize = 16 * 1024 * 1024; +/// FUSE recommends at least 128k, max 16M. Linux defaults to 128k. +pub(crate) const MAX_WRITE_SIZE: usize = 16 * 1024 * 1024; /// Size of the buffer for reading a request from the kernel. Since the kernel may send /// up to `MAX_WRITE_SIZE` bytes in a write request, we use that value plus some extra space. -const BUFFER_SIZE: usize = MAX_WRITE_SIZE + 4096; +const BUFFER_SIZE: usize = FUSE_REQUEST_BUFFER_SIZE; #[derive(Default, Debug, Eq, PartialEq)] /// How requests should be filtered based on the calling UID. -pub enum SessionACL { +pub(crate) enum SessionACL { /// Allow requests from any user. Corresponds to the `allow_other` mount option. All, /// Allow requests from root. Corresponds to the `allow_root` mount option. @@ -53,7 +54,7 @@ pub enum SessionACL { /// The session data structure #[derive(Debug)] -pub struct Session { +pub(crate) struct Session { /// Shared session state and filesystem operation implementations. pub(crate) shared: Arc>, /// Communication channel to the kernel driver @@ -64,6 +65,14 @@ pub struct Session { notify_tx: Option>, /// Receiver half — moved to the notify thread in run() notify_rx: Option>, + /// Request dispatch mode parsed by the FUSE adapter config. + dispatch_mode: DispatchMode, + /// FUSE-over-io_uring settings parsed by the FUSE adapter config. + #[cfg(target_os = "linux")] + uring_config: UringConfig, + /// Shutdown/join control for optional FUSE-over-io_uring queue threads. + #[cfg(target_os = "linux")] + uring_control: Arc, } #[derive(Debug)] @@ -120,169 +129,6 @@ impl SessionShared { } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -enum FuseDispatchMode { - Serial, - Parallel { - workers: usize, - queue_capacity: usize, - }, -} - -impl FuseDispatchMode { - fn from_env() -> Self { - // Tier Three Axis F: lift the default CPU/memory share from 25% to 50%. - // The previous 25% default chose 3 workers on a 14-core box and - // saturated under git-clone fork/fsync storms (`fuse_dispatch_wait_nanos` - // hit ~570 ms on the canonical mixed workload). 50% gives 7 workers on - // the same machine and trims dispatch wait roughly proportionally with - // no observed downside on Phase 8 stress gates. - const DEFAULT_AUTO_PERCENT: u8 = 50; - let workers = match std::env::var("AGENTFS_FUSE_WORKERS") { - Ok(value) if value.eq_ignore_ascii_case("serial") => return Self::Serial, - Ok(value) if value.eq_ignore_ascii_case("auto") => workers_from_resource_percent( - env_percent("AGENTFS_FUSE_CPU_PERCENT", DEFAULT_AUTO_PERCENT), - env_percent("AGENTFS_FUSE_MEMORY_PERCENT", DEFAULT_AUTO_PERCENT), - ), - Ok(value) => parse_workers(&value).unwrap_or_else(|| { - tracing::warn!( - value, - "invalid AGENTFS_FUSE_WORKERS; using serial FUSE dispatch" - ); - 0 - }), - // Default (unset): resolve as if AGENTFS_FUSE_WORKERS=auto so the - // kernel-cache fast path is on by default. Pair this with the - // matching default flip in cli/src/fuse.rs::fuse_workers_serial_from_env. - Err(_) => workers_from_resource_percent( - env_percent("AGENTFS_FUSE_CPU_PERCENT", DEFAULT_AUTO_PERCENT), - env_percent("AGENTFS_FUSE_MEMORY_PERCENT", DEFAULT_AUTO_PERCENT), - ), - }; - if workers == 0 { - return Self::Serial; - } - let default_queue_capacity = default_queue_capacity(workers); - let queue_capacity = match std::env::var("AGENTFS_FUSE_QUEUE") { - Ok(value) => parse_queue_capacity(&value, workers).unwrap_or_else(|| { - tracing::warn!( - value, - default_queue_capacity, - "invalid AGENTFS_FUSE_QUEUE; using default queue capacity" - ); - default_queue_capacity - }), - Err(_) => default_queue_capacity, - }; - - Self::Parallel { - workers, - queue_capacity, - } - } -} - -fn parse_workers(value: &str) -> Option { - let value = value.trim(); - if let Some(percent) = parse_percent_suffix(value) { - return Some(workers_from_resource_percent( - percent, - env_percent("AGENTFS_FUSE_MEMORY_PERCENT", percent), - )); - } - value.parse::().ok().filter(|workers| *workers > 0) -} - -fn parse_queue_capacity(value: &str, workers: usize) -> Option { - let value = value.trim(); - if let Some(percent) = parse_percent_suffix(value) { - return Some(queue_capacity_for_memory_percent(workers, percent)); - } - value.parse::().ok().filter(|queue| *queue > 0) -} - -fn parse_percent_suffix(value: &str) -> Option { - let percent = value.strip_suffix('%')?.trim().parse::().ok()?; - (1..=100).contains(&percent).then_some(percent) -} - -fn env_percent(name: &str, default: u8) -> u8 { - match std::env::var(name) { - Ok(value) => parse_percent_suffix(&format!("{}%", value.trim())) - .or_else(|| { - value - .trim() - .parse::() - .ok() - .filter(|v| (1..=100).contains(v)) - }) - .unwrap_or_else(|| { - tracing::warn!( - name, - value, - default, - "invalid percent environment variable; using default" - ); - default - }), - Err(_) => default, - } -} - -fn workers_from_resource_percent(cpu_percent: u8, memory_percent: u8) -> usize { - let cpu_workers = thread::available_parallelism() - .map(|parallelism| percent_of_count(parallelism.get(), cpu_percent)) - .unwrap_or(1); - let memory_workers = available_memory_bytes() - .map(|bytes| { - let budget = percent_of_bytes(bytes, memory_percent); - (budget / BUFFER_SIZE as u64).max(1) as usize - }) - .unwrap_or(cpu_workers); - cpu_workers.min(memory_workers).max(1) -} - -fn default_queue_capacity(workers: usize) -> usize { - let memory_percent = env_percent("AGENTFS_FUSE_QUEUE_MEMORY_PERCENT", 25); - workers - .saturating_mul(4) - .max(1) - .min(queue_capacity_for_memory_percent(workers, memory_percent)) -} - -fn queue_capacity_for_memory_percent(workers: usize, percent: u8) -> usize { - let Some(bytes) = available_memory_bytes() else { - return workers.saturating_mul(4).max(1); - }; - let budget = percent_of_bytes(bytes, percent); - let worker_bytes = workers.saturating_mul(BUFFER_SIZE) as u64; - let queue_budget = budget.saturating_sub(worker_bytes); - (queue_budget / BUFFER_SIZE as u64).max(1) as usize -} - -fn percent_of_count(count: usize, percent: u8) -> usize { - ((count as u64 * percent as u64) / 100).max(1) as usize -} - -fn percent_of_bytes(bytes: u64, percent: u8) -> u64 { - bytes.saturating_mul(percent as u64) / 100 -} - -fn available_memory_bytes() -> Option { - #[cfg(target_os = "linux")] - { - let meminfo = std::fs::read_to_string("/proc/meminfo").ok()?; - for line in meminfo.lines() { - let Some(rest) = line.strip_prefix("MemAvailable:") else { - continue; - }; - let kib = rest.split_whitespace().next()?.parse::().ok()?; - return kib.checked_mul(1024); - } - } - None -} - #[derive(Debug)] struct QueuedRequest { request: Request, @@ -317,7 +163,7 @@ fn dispatch_request( request: Request, ) { let concurrent = active_dispatches.fetch_add(1, Ordering::AcqRel) + 1; - agentfs_sdk::profiling::record_fuse_dispatch_concurrency(concurrent); + crate::telemetry::record_fuse_dispatch_concurrency(concurrent); let _guard = ActiveDispatchGuard { active_dispatches }; request.dispatch(shared); } @@ -327,8 +173,8 @@ fn dispatch_queued_request( active_dispatches: &AtomicU64, queued: QueuedRequest, ) { - agentfs_sdk::profiling::record_fuse_dispatch_parallel_task(); - agentfs_sdk::profiling::record_fuse_dispatch_wait(queued.enqueued_at.elapsed()); + crate::telemetry::record_fuse_dispatch_parallel_task(); + crate::telemetry::record_fuse_dispatch_wait(queued.enqueued_at.elapsed()); dispatch_request(shared, active_dispatches, queued.request); } @@ -356,10 +202,12 @@ impl Session { /// Create a new session by mounting the given filesystem to the given mountpoint /// # Errors /// Returns an error if the options are incorrect, or if the fuse device can't be mounted. - pub fn new>( + pub(crate) fn new>( filesystem: FS, mountpoint: P, options: &[MountOption], + dispatch_mode: DispatchMode, + uring_config: UringConfig, ) -> io::Result> { let mountpoint = mountpoint.as_ref(); debug!("Mounting {}", mountpoint.display()); @@ -397,30 +245,21 @@ impl Session { mount: Arc::new(Mutex::new(Some((mountpoint.to_owned(), mount)))), notify_tx: Some(notify_tx), notify_rx: Some(notify_rx), + dispatch_mode, + #[cfg(target_os = "linux")] + uring_config, + #[cfg(target_os = "linux")] + uring_control: super::uring::UringQueueControl::new(), }) } - /// Wrap an existing /dev/fuse file descriptor. This doesn't mount the - /// filesystem anywhere; that must be done separately. - pub fn from_fd(filesystem: FS, fd: OwnedFd, acl: SessionACL) -> Self { - let ch = Channel::new(Arc::new(fd.into())); - let (notify_tx, notify_rx) = mpsc::channel(); - Session { - shared: Arc::new(SessionShared::new(filesystem, acl, geteuid().as_raw())), - ch, - mount: Arc::new(Mutex::new(None)), - notify_tx: Some(notify_tx), - notify_rx: Some(notify_rx), - } - } - /// Run the session loop that receives kernel requests and dispatches them to method /// calls into the filesystem. This read-dispatch-loop is non-concurrent to prevent /// having multiple buffers (which take up much memory), but the filesystem methods /// may run concurrent by spawning threads. /// # Errors /// Returns any final error when the session comes to an end. - pub fn run(&mut self) -> io::Result<()> { + pub(crate) fn run(&mut self) -> io::Result<()> { let notify_rx = self.notify_rx.take().expect("run() called more than once"); let notifier = self.notifier(); let notify_handle = thread::spawn(move || { @@ -449,22 +288,23 @@ impl Session { // regular requests; this legacy loop keeps running for INIT, FORGET, // INTERRUPT and as fallback when the kernel rejects ring setup. #[cfg(target_os = "linux")] - if super::uring::uring_enabled() { + if self.uring_config.enabled { super::uring::start_uring_queues( self.shared.clone(), deferred.clone(), self.ch.device(), + self.uring_control.clone(), + self.uring_config, ); } - let dispatch_mode = FuseDispatchMode::from_env(); - let result = match dispatch_mode { - FuseDispatchMode::Serial => { + let result = match self.dispatch_mode { + DispatchMode::Serial => { tracing::info!("resolved FUSE dispatch mode: serial"); - agentfs_sdk::profiling::set_fuse_workers_configured(0); + crate::telemetry::set_fuse_workers_configured(0); self.run_serial(deferred.clone()) } - FuseDispatchMode::Parallel { + DispatchMode::Parallel { workers, queue_capacity, } => { @@ -473,11 +313,14 @@ impl Session { queue_capacity, "resolved FUSE dispatch mode: parallel" ); - agentfs_sdk::profiling::set_fuse_workers_configured(workers as u64); + crate::telemetry::set_fuse_workers_configured(workers as u64); self.run_parallel(deferred.clone(), workers, queue_capacity) } }; + #[cfg(target_os = "linux")] + self.uring_control.shutdown_and_join(); + // Drop all senders to close the channel, then join the notify thread // to ensure in-flight invalidations are flushed before returning. drop(deferred); @@ -547,11 +390,11 @@ impl Session { let lane_depth = lane_depths[lane].fetch_add(1, Ordering::AcqRel) + 1; match lane_senders[lane].try_send(queued) { Ok(()) => { - agentfs_sdk::profiling::record_fuse_worker_queue_depth(depth); + crate::telemetry::record_fuse_worker_queue_depth(depth); Ok(()) } Err(mpsc::TrySendError::Full(queued)) => { - agentfs_sdk::profiling::record_fuse_dispatch_inline_fallback(); + crate::telemetry::record_fuse_dispatch_inline_fallback(); lane_senders[lane].send(queued).map_err(|_| { queue_depth.fetch_sub(1, Ordering::AcqRel); lane_depths[lane].fetch_sub(1, Ordering::AcqRel); @@ -560,15 +403,15 @@ impl Session { "FUSE dispatch worker queue disconnected", ) })?; - agentfs_sdk::profiling::record_fuse_worker_queue_depth(depth); - agentfs_sdk::profiling::record_fuse_worker_queue_depth(lane_depth); + crate::telemetry::record_fuse_worker_queue_depth(depth); + crate::telemetry::record_fuse_worker_queue_depth(lane_depth); Ok(()) } Err(mpsc::TrySendError::Disconnected(queued)) => { queue_depth.fetch_sub(1, Ordering::AcqRel); lane_depths[lane].fetch_sub(1, Ordering::AcqRel); drop(queued); - agentfs_sdk::profiling::record_fuse_dispatch_inline_fallback(); + crate::telemetry::record_fuse_dispatch_inline_fallback(); Err(io::Error::new( io::ErrorKind::BrokenPipe, "FUSE dispatch worker queue disconnected", @@ -626,38 +469,107 @@ impl Session { Ok(()) } - /// Unmount the filesystem - pub fn unmount(&mut self) { - drop(std::mem::take(&mut *self.mount.lock().unwrap())); - } - /// Returns a thread-safe object that can be used to unmount the Filesystem - pub fn unmount_callable(&mut self) -> SessionUnmounter { + pub(crate) fn unmount_callable(&mut self) -> SessionUnmounter { SessionUnmounter { mount: self.mount.clone(), + device: self.ch.device(), + #[cfg(target_os = "linux")] + uring_control: self.uring_control.clone(), } } /// Returns an object that can be used to send notifications to the kernel - pub fn notifier(&self) -> Notifier { + pub(crate) fn notifier(&self) -> Notifier { Notifier::new(self.ch.sender()) } } #[derive(Debug)] /// A thread-safe object that can be used to unmount a Filesystem -pub struct SessionUnmounter { +pub(crate) struct SessionUnmounter { mount: Arc>>, + device: Arc, + #[cfg(target_os = "linux")] + uring_control: Arc, } impl SessionUnmounter { /// Unmount the filesystem - pub fn unmount(&mut self) -> io::Result<()> { + pub(crate) fn unmount(&mut self) -> io::Result<()> { + #[cfg(target_os = "linux")] + self.uring_control.shutdown_and_join(); + #[cfg(target_os = "linux")] + if let Err(err) = abort_fuse_connection(&self.device) { + debug!("failed to abort FUSE connection during unmount: {err}"); + } drop(std::mem::take(&mut *self.mount.lock().unwrap())); Ok(()) } } +#[cfg(target_os = "linux")] +fn abort_fuse_connection(device: &std::fs::File) -> io::Result<()> { + // Only a still-connected (wedged) connection needs the fusectl abort. An + // already-dead connection keeps its id in fdinfo, but the kernel frees + // that id at unmount and hands it to the next FUSE mount — writing the + // abort then kills an unrelated fresh mount (observed as a silent + // mount-then-exit during rapid unmount-then-mount cycles). + if connection_is_aborted(device) { + return Ok(()); + } + let fdinfo_path = format!("/proc/self/fdinfo/{}", device.as_raw_fd()); + let fdinfo = std::fs::read_to_string(fdinfo_path)?; + let Some(connection_id) = fdinfo.lines().find_map(|line| { + line.strip_prefix("fuse_connection:") + .and_then(|value| value.split_whitespace().next()) + }) else { + return Ok(()); + }; + let abort_path = format!("/sys/fs/fuse/connections/{connection_id}/abort"); + match std::fs::write(&abort_path, b"1\n") { + Ok(()) => Ok(()), + Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(()), + Err(err) => Err(err), + } +} + +/// Whether the FUSE connection behind `device` is already disconnected +/// (fuse_dev_poll reports `EPOLLERR` once `fc->connected` drops). +#[cfg(target_os = "linux")] +fn connection_is_aborted(device: &std::fs::File) -> bool { + connection_is_aborted_by(device.as_raw_fd(), |poll_fd| { + match unsafe { libc::poll(poll_fd, 1, 0) } { + -1 => Err(io::Error::last_os_error()), + result => Ok(result), + } + }) +} + +/// Fail closed: only a successful poll can positively confirm the connection +/// is still alive, and writing the fusectl abort after the connection id has +/// been freed and recycled kills an unrelated fresh mount, so a non-EINTR +/// poll error reports the connection as aborted and the caller skips the +/// abort. +#[cfg(target_os = "linux")] +fn connection_is_aborted_by( + fd: std::os::fd::RawFd, + mut poll: impl FnMut(&mut libc::pollfd) -> io::Result, +) -> bool { + let mut poll_fd = libc::pollfd { + fd, + events: 0, + revents: 0, + }; + loop { + match poll(&mut poll_fd) { + Ok(result) => return result == 1 && (poll_fd.revents & libc::POLLERR) != 0, + Err(error) if error.kind() == io::ErrorKind::Interrupted => continue, + Err(_) => return true, + } + } +} + fn aligned_sub_buf(buf: &mut [u8], alignment: usize) -> &mut [u8] { let off = alignment - (buf.as_ptr() as usize) % alignment; if off == alignment { @@ -667,13 +579,6 @@ fn aligned_sub_buf(buf: &mut [u8], alignment: usize) -> &mut [u8] { } } -impl Session { - /// Run the session loop in a background thread - pub fn spawn(self) -> io::Result { - BackgroundSession::new(self) - } -} - impl Drop for Session { fn drop(&mut self) { if !self.shared.is_destroyed() { @@ -687,50 +592,46 @@ impl Drop for Session { } } -/// The background session data structure -#[derive(Debug)] -pub struct BackgroundSession { - /// Thread guard of the background session - pub guard: JoinHandle>, - /// Object for creating Notifiers for client use - sender: ChannelSender, - /// Ensures the filesystem is unmounted when the session ends - _mount: Option, -} +#[cfg(all(test, target_os = "linux"))] +mod tests { + use super::*; + + #[test] + fn poll_error_reports_connection_aborted() { + let aborted = + connection_is_aborted_by(-1, |_| Err(io::Error::from_raw_os_error(libc::EIO))); + assert!( + aborted, + "a poll error cannot confirm liveness; the fusectl abort must be skipped" + ); + } -impl BackgroundSession { - /// Create a new background session for the given session by running its - /// session loop in a background thread. If the returned handle is dropped, - /// the filesystem is unmounted and the given session ends. - pub fn new(se: Session) -> io::Result { - let sender = se.ch.sender(); - // Take the fuse_session, so that we can unmount it - let mount = std::mem::take(&mut *se.mount.lock().unwrap()).map(|(_, mount)| mount); - let guard = thread::spawn(move || { - let mut se = se; - se.run() + #[test] + fn eintr_poll_retries_until_a_definitive_answer() { + let mut calls = 0; + let aborted = connection_is_aborted_by(-1, |_| { + calls += 1; + if calls == 1 { + Err(io::Error::from_raw_os_error(libc::EINTR)) + } else { + Ok(0) + } }); - Ok(BackgroundSession { - guard, - sender, - _mount: mount, - }) + assert!(!aborted); + assert_eq!(calls, 2); } - /// Unmount the filesystem and join the background thread. - /// # Panics - /// Panics if the background thread can't be recovered (e.g., because it panicked). - pub fn join(self) { - let Self { - guard, - sender: _, - _mount, - } = self; - drop(_mount); - guard.join().unwrap().unwrap(); + + #[test] + fn pollerr_revent_reports_connection_aborted() { + let aborted = connection_is_aborted_by(-1, |poll_fd| { + poll_fd.revents = libc::POLLERR; + Ok(1) + }); + assert!(aborted); } - /// Returns an object that can be used to send notifications to the kernel - pub fn notifier(&self) -> Notifier { - Notifier::new(self.sender.clone()) + #[test] + fn quiet_live_connection_is_not_aborted() { + assert!(!connection_is_aborted_by(-1, |_| Ok(0))); } } diff --git a/cli/src/fuser/uring.rs b/crates/agentfs-fuse/src/transport/uring.rs similarity index 78% rename from cli/src/fuser/uring.rs rename to crates/agentfs-fuse/src/transport/uring.rs index 7360dd0a..c74a71b3 100644 --- a/cli/src/fuser/uring.rs +++ b/crates/agentfs-fuse/src/transport/uring.rs @@ -21,9 +21,8 @@ //! reply synchronously, so each ring's submission queue is effectively //! single-threaded (guarded by a mutex that is never contended in practice). //! -//! Default on when the kernel side is available (root sysctl -//! `fuse.enable_uring=1`); kill switch `AGENTFS_FUSE_URING=0`; depth per -//! queue via `AGENTFS_FUSE_URING_DEPTH` (default 4). +//! Runtime enable/depth/spin settings are parsed by `crate::adapter::config` and +//! passed into the session. This transport reads no environment variables. #![cfg(target_os = "linux")] @@ -32,9 +31,12 @@ use std::io; use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd}; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; +use std::thread::JoinHandle; use std::time::Duration; -use log::{debug, error, warn}; +use tracing::{debug, error, warn}; + +use crate::adapter::config::UringConfig; use super::channel::ChannelSender; use super::deferred_notify::DeferredNotifier; @@ -53,12 +55,13 @@ const IORING_SETUP_SQE128: u32 = 1 << 10; const IORING_FEAT_SINGLE_MMAP: u32 = 1 << 0; const IORING_OFF_SQ_RING: i64 = 0; -const IORING_OFF_CQ_RING: i64 = 0x800_0000; const IORING_OFF_SQES: i64 = 0x1000_0000; const IORING_ENTER_GETEVENTS: u32 = 1; +const IORING_OP_NOP: u8 = 0; const IORING_OP_URING_CMD: u8 = 46; +const SHUTDOWN_WAKE_USER_DATA: u64 = u64::MAX; #[repr(C)] #[derive(Default, Clone, Copy)] @@ -108,12 +111,11 @@ struct IoUringParams { const FUSE_IO_URING_CMD_REGISTER: u32 = 1; const FUSE_IO_URING_CMD_COMMIT_AND_FETCH: u32 = 2; -/// `fuse_uring_req_header` layout: 128B in_out (fuse_in/out_header), 128B -/// op_in (first request argument), 32B `fuse_uring_ent_in_out`. +/// Header layout: 128B in_out (fuse_in/out_header), 128B op_in +/// (first request argument), 32B ring-entry metadata. const HDR_IN_OUT_OFFSET: usize = 0; const HDR_OP_IN_OFFSET: usize = 128; const HDR_ENT_OFFSET: usize = 256; -const HDR_STRUCT_SIZE: usize = 288; /// The kernel copies the first request argument into the 128-byte op_in area /// without bounds-checking against it (names up to 255 bytes overflow into /// and past `ent_in_out`). Oversize the header buffer so the overflow stays @@ -133,40 +135,6 @@ const MAX_OP_IN_SIZE: usize = 128; pub(crate) const URING_MAX_WRITE: u32 = 1 << 20; const PAYLOAD_BUF_SIZE: usize = (URING_MAX_WRITE as usize) + 4096; -// ─── configuration ────────────────────────────────────────────────────────── - -/// Default on: codex A/B showed the transport equal-or-better on every -/// phase (total 3.37x -> 2.92x). Safe unconditionally because INIT only -/// advertises FUSE_OVER_IO_URING after a ring-setup probe succeeds, which -/// requires the root sysctl `fuse.enable_uring=1`; everything else falls -/// back to the legacy /dev/fuse channel. `AGENTFS_FUSE_URING=0` is the -/// kill switch. -pub(crate) fn uring_enabled() -> bool { - !matches!( - std::env::var("AGENTFS_FUSE_URING").as_deref(), - Ok("0") | Ok("false") | Ok("off") - ) -} - -fn uring_queue_depth() -> usize { - std::env::var("AGENTFS_FUSE_URING_DEPTH") - .ok() - .and_then(|v| v.parse::().ok()) - .filter(|d| (1..=64).contains(d)) - .unwrap_or(4) -} - -/// Busy-poll the completion queue for this long before blocking in -/// io_uring_enter, trading idle CPU for wakeup latency on request bursts. -/// Default 0 (no spin). -fn uring_spin_us() -> u64 { - std::env::var("AGENTFS_FUSE_URING_SPIN_US") - .ok() - .and_then(|v| v.parse::().ok()) - .filter(|us| *us <= 1000) - .unwrap_or(0) -} - /// One queue per possible CPU: the kernel sizes its queue array with /// `num_possible_cpus()` and routes requests by `task_cpu(current)`. fn possible_cpus() -> usize { @@ -213,7 +181,6 @@ struct RawRing { sq_ring_size: usize, sqes_ptr: *mut u8, sqes_size: usize, - sq_khead: *const AtomicU32, sq_ktail: *const AtomicU32, sq_mask: u32, sq_array: *mut u32, @@ -265,7 +232,6 @@ impl RawRing { let at = |off: u32| unsafe { sq_ring_ptr.add(off as usize) }; let ring = RawRing { - sq_khead: at(params.sq_off.head).cast::(), sq_ktail: at(params.sq_off.tail).cast::(), sq_mask: unsafe { *at(params.sq_off.ring_mask).cast::() }, sq_array: at(params.sq_off.array).cast::(), @@ -373,7 +339,7 @@ fn enter(ring_fd: RawFd, to_submit: u32, min_complete: u32) -> io::Result<()> { } } -/// Build the 128-byte uring_cmd SQE carrying `struct fuse_uring_cmd_req`. +/// Build the 128-byte uring_cmd SQE carrying the FUSE uring command request. fn build_cmd_sqe( dev_fd: RawFd, cmd_op: u32, @@ -390,13 +356,20 @@ fn build_cmd_sqe( sqe[16..24].copy_from_slice(&addr.to_le_bytes()); sqe[24..28].copy_from_slice(&len.to_le_bytes()); sqe[32..40].copy_from_slice(&user_data.to_le_bytes()); - // struct fuse_uring_cmd_req in the 80-byte SQE128 command area. + // FUSE uring command request in the 80-byte SQE128 command area. sqe[48..56].copy_from_slice(&0u64.to_le_bytes()); // flags sqe[56..64].copy_from_slice(&commit_id.to_le_bytes()); sqe[64..66].copy_from_slice(&qid.to_le_bytes()); sqe } +fn build_nop_sqe(user_data: u64) -> [u8; 128] { + let mut sqe = [0u8; 128]; + sqe[0] = IORING_OP_NOP; + sqe[32..40].copy_from_slice(&user_data.to_le_bytes()); + sqe +} + // ─── queue state ───────────────────────────────────────────────────────────── struct EntryBufs { @@ -429,12 +402,129 @@ impl std::fmt::Debug for QueueShared { } } +#[derive(Debug)] +pub(crate) struct UringQueueControl { + shutdown: AtomicBool, + starter: Mutex>>, + handles: Mutex>>, + queues: Mutex>>, + legacy_fallback_logged: AtomicBool, +} + +impl UringQueueControl { + pub(crate) fn new() -> Arc { + Arc::new(Self { + shutdown: AtomicBool::new(false), + starter: Mutex::new(None), + handles: Mutex::new(Vec::new()), + queues: Mutex::new(Vec::new()), + legacy_fallback_logged: AtomicBool::new(false), + }) + } + + fn is_shutdown(&self) -> bool { + self.shutdown.load(Ordering::Acquire) + } + + /// The "starting fuse-over-io_uring queues" INFO line precedes kernel + /// acceptance of the REGISTER commands, so a rejected registration needs + /// its own INFO counterpart or the log claims a transport that never ran. + /// Every per-CPU queue hits the same rejection; log the session-level + /// fallback once. + fn log_legacy_fallback_once(&self) { + if !self.legacy_fallback_logged.swap(true, Ordering::AcqRel) { + tracing::info!( + "kernel rejected fuse-over-io_uring registration; \ + serving this mount on the legacy /dev/fuse channel" + ); + } + } + + fn set_starter(&self, handle: JoinHandle<()>) { + *self.starter.lock().unwrap() = Some(handle); + } + + fn push_handle(&self, handle: JoinHandle<()>) { + self.handles.lock().unwrap().push(handle); + } + + fn register_queue(&self, queue: Arc) { + if self.is_shutdown() { + wake_queue(&queue); + } + self.queues.lock().unwrap().push(queue); + } + + pub(crate) fn shutdown(&self) { + self.shutdown.store(true, Ordering::Release); + let queues = self.queues.lock().unwrap().clone(); + for queue in queues { + wake_queue(&queue); + } + } + + pub(crate) fn shutdown_and_join(&self) { + self.shutdown(); + + if let Some(starter) = self.starter.lock().unwrap().take() { + if let Err(e) = starter.join() { + warn!("fuse-uring: starter thread panicked: {e:?}"); + } + } + + let handles = std::mem::take(&mut *self.handles.lock().unwrap()); + for handle in handles { + if let Err(e) = handle.join() { + warn!("fuse-uring: queue thread panicked: {e:?}"); + } + } + + self.queues.lock().unwrap().clear(); + } +} + +fn wake_queue(queue: &Arc) { + let ring_fd = { + let mut ring = queue.ring.lock().unwrap(); + ring.push_sqe(&build_nop_sqe(SHUTDOWN_WAKE_USER_DATA)); + ring.fd.as_raw_fd() + }; + if let Err(e) = enter(ring_fd, 1, 0) { + debug!("fuse-uring: shutdown wake failed on qid={}: {e}", queue.qid); + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum CqeUserData { + Slot(usize), + ShutdownWake, + Invalid(u64), +} + +fn classify_cqe_user_data(user_data: u64, depth: usize) -> CqeUserData { + if user_data == SHUTDOWN_WAKE_USER_DATA { + return CqeUserData::ShutdownWake; + } + + match usize::try_from(user_data) { + Ok(slot) if slot < depth => CqeUserData::Slot(slot), + _ => CqeUserData::Invalid(user_data), + } +} + +#[derive(Debug, Clone, Copy)] +struct QueueThreadConfig { + qid: u16, + depth: usize, + spin: Duration, +} + /// Per-request reply target: commits the reply into the ring entry's buffers /// and queues a COMMIT_AND_FETCH SQE. Replies happen inline on the queue /// thread (handlers are synchronous), so the submission mutex is uncontended; /// the next loop iteration submits it. #[derive(Debug, Clone)] -pub struct UringSender { +pub(crate) struct UringSender { queue: Arc, slot: usize, commit_id: u64, @@ -512,16 +602,23 @@ pub(crate) fn start_uring_queues( shared: Arc>, deferred: Arc, device: Arc, + control: Arc, + config: UringConfig, ) { - let depth = uring_queue_depth(); + let depth = config.depth; + let spin = Duration::from_micros(config.spin_us); let nr_queues = possible_cpus(); let active_dispatches = Arc::new(AtomicU64::new(0)); + let starter_control = control.clone(); let starter = move || { // REGISTER needs the kernel-side fc->initialized; our INIT reply also // races the kernel's processing of it, so the per-queue registration // loop additionally retries on EAGAIN. let wait_start = std::time::Instant::now(); while !shared.is_initialized() { + if starter_control.is_shutdown() { + return; + } if wait_start.elapsed() > Duration::from_secs(30) { warn!("fuse-uring: session not initialized after 30s; not starting rings"); return; @@ -534,40 +631,55 @@ pub(crate) fn start_uring_queues( let deferred = deferred.clone(); let device = device.clone(); let active_dispatches = active_dispatches.clone(); - if let Err(e) = std::thread::Builder::new() + let control = starter_control.clone(); + if control.is_shutdown() { + return; + } + match std::thread::Builder::new() .name(format!("agentfs-fuse-uring-{qid}")) .spawn(move || { - queue_thread( - qid as u16, + let queue_config = QueueThreadConfig { + qid: qid as u16, depth, + spin, + }; + queue_thread( + queue_config, shared, deferred, device, active_dispatches, + control, ) - }) - { - error!("fuse-uring: failed to spawn queue thread {qid}: {e}"); - return; + }) { + Ok(handle) => starter_control.push_handle(handle), + Err(e) => { + error!("fuse-uring: failed to spawn queue thread {qid}: {e}"); + return; + } } } }; - if let Err(e) = std::thread::Builder::new() + match std::thread::Builder::new() .name("agentfs-fuse-uring-start".into()) .spawn(starter) { - error!("fuse-uring: failed to spawn starter thread: {e}"); + Ok(handle) => control.set_starter(handle), + Err(e) => error!("fuse-uring: failed to spawn starter thread: {e}"), } } fn queue_thread( - qid: u16, - depth: usize, + config: QueueThreadConfig, shared: Arc>, deferred: Arc, device: Arc, active_dispatches: Arc, + control: Arc, ) { + let qid = config.qid; + let depth = config.depth; + let spin = config.spin; let ring = match RawRing::new((depth + 1) as u32) { Ok(ring) => ring, Err(e) => { @@ -602,6 +714,7 @@ fn queue_thread( pending_submit: AtomicU32::new(0), device, }); + control.register_queue(queue.clone()); let register_sqe = |slot: usize| { let ent = queue.entries[slot].lock().unwrap(); @@ -627,9 +740,11 @@ fn queue_thread( let mut dead = 0usize; let mut register_retries = 0u32; let ring_fd = queue.ring.lock().unwrap().fd.as_raw_fd(); - let spin = Duration::from_micros(uring_spin_us()); - loop { + if control.is_shutdown() { + debug!("fuse-uring: queue {qid} shutting down"); + return; + } // Submit pending SQEs immediately, then optionally busy-poll the CQ // before blocking: the wakeup from a blocking enter costs more than // a typical request inter-arrival gap on hot paths. @@ -657,10 +772,26 @@ fn queue_thread( error!("fuse-uring: io_uring_enter failed on qid={qid}: {e}"); return; } + if control.is_shutdown() { + debug!("fuse-uring: queue {qid} shutting down"); + return; + } loop { let cqe = queue.ring.lock().unwrap().pop_cqe(); let Some(cqe) = cqe else { break }; - let slot = cqe.user_data as usize; + let slot = match classify_cqe_user_data(cqe.user_data, depth) { + CqeUserData::Slot(slot) => slot, + CqeUserData::ShutdownWake => { + debug!("fuse-uring: queue {qid} received shutdown wake"); + return; + } + CqeUserData::Invalid(user_data) => { + warn!( + "fuse-uring: queue {qid} received CQE with invalid user_data={user_data} depth={depth}" + ); + continue; + } + }; if cqe.res < 0 { match -cqe.res { libc::EAGAIN if register_retries < 10_000 => { @@ -672,6 +803,7 @@ fn queue_thread( queue.pending_submit.fetch_add(1, Ordering::AcqRel); } libc::EOPNOTSUPP => { + control.log_legacy_fallback_once(); debug!("fuse-uring: not supported for this connection (qid={qid})"); return; } @@ -740,7 +872,7 @@ fn handle_request( (buf, commit_id, unique) }; - agentfs_sdk::profiling::record_fuse_uring_request(); + crate::telemetry::record_fuse_uring_request(); let sender = ChannelSender::Uring(UringSender { queue: queue.clone(), @@ -753,7 +885,7 @@ fn handle_request( // Mirror the legacy worker pool's concurrency accounting so the // serialization gates observe uring-side parallelism too. let concurrent = active_dispatches.fetch_add(1, Ordering::AcqRel) + 1; - agentfs_sdk::profiling::record_fuse_dispatch_concurrency(concurrent); + crate::telemetry::record_fuse_dispatch_concurrency(concurrent); request.dispatch(shared); active_dispatches.fetch_sub(1, Ordering::AcqRel); // Every op the kernel routes through uring expects a reply @@ -785,3 +917,23 @@ fn reply_error_raw(queue: &Arc, slot: usize, commit_id: u64, unique error!("fuse-uring: failed to commit error reply: {e}"); } } + +#[cfg(test)] +mod tests { + use super::{classify_cqe_user_data, CqeUserData, SHUTDOWN_WAKE_USER_DATA}; + + #[test] + fn shutdown_wake_user_data_is_not_a_ring_slot() { + assert_eq!( + classify_cqe_user_data(SHUTDOWN_WAKE_USER_DATA, 4), + CqeUserData::ShutdownWake + ); + } + + #[test] + fn cqe_user_data_must_name_registered_ring_slot() { + assert_eq!(classify_cqe_user_data(0, 4), CqeUserData::Slot(0)); + assert_eq!(classify_cqe_user_data(3, 4), CqeUserData::Slot(3)); + assert_eq!(classify_cqe_user_data(4, 4), CqeUserData::Invalid(4)); + } +} diff --git a/crates/agentfs-mount/Cargo.toml b/crates/agentfs-mount/Cargo.toml new file mode 100644 index 00000000..13750aa4 --- /dev/null +++ b/crates/agentfs-mount/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "agentfs-mount" +version.workspace = true +edition.workspace = true +description = "Mount lifecycle and supervision for AgentFS" +license.workspace = true + +[lints] +workspace = true + +[dependencies] +agentfs-core = { workspace = true } +agentfs-nfs = { workspace = true } +anyhow = { workspace = true } +async-trait = { workspace = true } +libc = { workspace = true } +tokio = { workspace = true } +tokio-util = { workspace = true } +tracing = { workspace = true } + +[target.'cfg(target_os = "linux")'.dependencies] +agentfs-fuse = { workspace = true } + +[dev-dependencies] +tempfile = { workspace = true } diff --git a/cli/src/daemon.rs b/crates/agentfs-mount/src/daemon.rs similarity index 83% rename from cli/src/daemon.rs rename to crates/agentfs-mount/src/daemon.rs index 12c9605d..10c24594 100644 --- a/cli/src/daemon.rs +++ b/crates/agentfs-mount/src/daemon.rs @@ -21,11 +21,33 @@ const MAX_ERROR_MSG_LEN: usize = 4096; /// # Returns /// * `Ok(())` in the parent process if the daemon started successfully /// * Never returns in the child process (exits with appropriate code) +/// +/// # Caller contract +/// No tokio runtime may be alive anywhere in the process when this is +/// called: `fork()` copies only the calling thread, so locks held by +/// runtime worker threads at fork time (allocator, runtime internals) stay +/// locked forever in the child. The guard below cannot enforce this fully — +/// `Handle::try_current()` is thread-local, so it only catches a runtime +/// context *entered on the calling thread* (inside `block_on` or an +/// `EnterGuard`). A runtime whose workers live on other threads, or one +/// constructed on this thread but not entered, evades it, and tokio's +/// public API offers no process-wide probe. Callers must therefore fork +/// first and build their runtime inside `daemon_fn`, dropping any +/// short-lived pre-check runtimes beforehand (see `agentfs mount`, whose +/// two call sites both follow this shape). pub fn daemonize(daemon_fn: F, ready_check: R, timeout: Duration) -> Result<()> where F: FnOnce() -> Result<()> + Send + 'static, R: Fn() -> bool, { + // Best-effort guard; thread-local only (see the caller contract above). + if tokio::runtime::Handle::try_current().is_ok() { + anyhow::bail!( + "daemonize called from inside a tokio runtime; fork before any runtime exists \ + and create the runtime inside the daemon function instead" + ); + } + // Create pipe for child->parent signaling let mut pipe_fds: [libc::c_int; 2] = [0; 2]; if unsafe { libc::pipe(pipe_fds.as_mut_ptr()) } != 0 { @@ -257,3 +279,18 @@ fn redirect_stdio() { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn daemonize_refuses_to_fork_inside_a_live_runtime() { + let err = daemonize(|| Ok(()), || true, Duration::from_secs(1)) + .expect_err("daemonize must refuse to fork from inside a tokio runtime"); + assert!( + err.to_string().contains("tokio runtime"), + "error must name the runtime constraint: {err:#}" + ); + } +} diff --git a/cli/src/mount/fuse.rs b/crates/agentfs-mount/src/fuse.rs similarity index 61% rename from cli/src/mount/fuse.rs rename to crates/agentfs-mount/src/fuse.rs index 78ef015c..e448ce99 100644 --- a/cli/src/mount/fuse.rs +++ b/crates/agentfs-mount/src/fuse.rs @@ -7,10 +7,16 @@ use std::sync::{ atomic::{AtomicU64, Ordering}, Arc, }; -use std::time::Instant; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use super::{wait_for_mount, MountBackend, MountHandle, MountHandleInner, MountOpts}; +use super::{wait_for_mount, Backend, MountHandle, MountHandleInner, MountOpts}; + +/// Extra margin past `MountOpts::timeout` for the readiness probe channel: +/// the probe thread itself bounds at `timeout` unless its stat call wedges. +const READY_PROBE_GRACE: std::time::Duration = std::time::Duration::from_secs(2); +/// How long the failure path waits for best-effort session teardown before +/// surfacing the readiness error anyway. +const READY_TEARDOWN_GRACE: std::time::Duration = std::time::Duration::from_secs(5); /// FUSE unmount implementation using fusermount. pub(super) fn unmount_fuse(mountpoint: &Path, lazy: bool) -> Result<()> { @@ -39,10 +45,10 @@ pub(super) fn unmount_fuse(mountpoint: &Path, lazy: bool) -> Result<()> { /// Internal FUSE mount implementation. pub(super) fn mount_fuse( - fs: Arc, + fs: Arc, opts: MountOpts, ) -> Result { - use crate::fuse::FuseMountOptions; + use agentfs_fuse::FuseMountOptions; let fuse_opts = FuseMountOptions { mountpoint: opts.mountpoint.clone(), @@ -58,23 +64,59 @@ pub(super) fn mount_fuse( let timeout = opts.timeout; let lazy_unmount = opts.lazy_unmount; - let fs_arc: Arc = Arc::new(ReadWriteLaneFsAdapter::new(fs)); - - let fuse_handle = std::thread::spawn(move || { - let rt = crate::get_runtime(); - crate::fuse::mount(fs_arc, fuse_opts, rt) - }); - - if !wait_for_mount(&mountpoint, timeout) { - anyhow::bail!("FUSE mount did not become ready within {:?}", timeout); + let fs_arc: Arc = Arc::new(ReadWriteLaneFsAdapter::new(fs)); + + let rt = crate::get_runtime(); + let mut fuse_session = agentfs_fuse::mount(fs_arc, fuse_opts, rt)?; + + // The readiness probe stats the mountpoint, which can block indefinitely + // when a fresh fuse-over-io_uring mount races the kernel-side drain of a + // just-closed connection and post-INIT requests stall (docs/MANUAL.md, + // "FUSE-over-io_uring"). Probing from a helper thread keeps this wait + // bounded either way, so a wedged mount surfaces as an error. + let (ready_tx, ready_rx) = std::sync::mpsc::channel(); + let probe_mountpoint = mountpoint.clone(); + let _ = std::thread::Builder::new() + .name("agentfs-mount-ready".into()) + .spawn(move || { + let _ = ready_tx.send(wait_for_mount(&probe_mountpoint, timeout)); + }); + let ready = ready_rx + .recv_timeout(timeout + READY_PROBE_GRACE) + .unwrap_or(false); + + if !ready { + // Teardown from a helper thread too: joining a wedged session could + // block, and the caller is owed a bounded, clear failure. + let (teardown_tx, teardown_rx) = std::sync::mpsc::channel(); + let _ = std::thread::Builder::new() + .name("agentfs-mount-teardown".into()) + .spawn(move || { + if let Err(error) = fuse_session.unmount() { + tracing::warn!(%error, "failed to unmount FUSE session after readiness timeout"); + } + if let Err(error) = fuse_session.join() { + tracing::warn!(%error, "FUSE session exited with error after readiness timeout"); + } + let _ = teardown_tx.send(()); + }); + let _ = teardown_rx.recv_timeout(READY_TEARDOWN_GRACE); + anyhow::bail!( + "FUSE mount at {} did not become ready within {:?}. On kernels with \ + fuse-over-io_uring enabled, a mount racing the drain of a just-closed \ + FUSE connection can stall; retry shortly, avoid rapid \ + unmount-then-mount cycles, or set AGENTFS_FUSE_URING=0.", + mountpoint.display(), + timeout + ); } Ok(MountHandle { mountpoint, - backend: MountBackend::Fuse, + backend: Backend::Fuse, lazy_unmount, inner: MountHandleInner::Fuse { - thread: Some(fuse_handle), + session: Some(fuse_session), }, }) } @@ -82,7 +124,7 @@ pub(super) fn mount_fuse( /// Adapter that admits read operations concurrently while serializing /// mutations before they reach the backend filesystem. struct ReadWriteLaneFsAdapter { - inner: Arc, + inner: Arc, lanes: RwLock<()>, active_reads: AtomicU64, } @@ -105,7 +147,7 @@ impl Drop for ReadLaneGuard<'_> { } impl ReadWriteLaneFsAdapter { - fn new(inner: Arc) -> Self { + fn new(inner: Arc) -> Self { Self { inner, lanes: RwLock::new(()), @@ -114,14 +156,9 @@ impl ReadWriteLaneFsAdapter { } async fn enter_read_lane(&self) -> ReadLaneGuard<'_> { - let started = agentfs_sdk::profiling::is_enabled().then(Instant::now); let guard = self.lanes.read().await; - if let Some(started) = started { - agentfs_sdk::profiling::record_fuse_read_lane_wait(started.elapsed()); - } - let active_reads = self.active_reads.fetch_add(1, Ordering::Relaxed) + 1; - agentfs_sdk::profiling::record_fuse_read_lane_concurrency(active_reads); + self.active_reads.fetch_add(1, Ordering::Relaxed); ReadLaneGuard { active_reads: &self.active_reads, @@ -130,12 +167,7 @@ impl ReadWriteLaneFsAdapter { } async fn enter_write_lane(&self) -> RwLockWriteGuard<'_, ()> { - let started = agentfs_sdk::profiling::is_enabled().then(Instant::now); - let guard = self.lanes.write().await; - if let Some(started) = started { - agentfs_sdk::profiling::record_fuse_write_lane_wait(started.elapsed()); - } - guard + self.lanes.write().await } async fn lock_read_fs(&self) -> ReadLaneGuard<'_> { @@ -148,12 +180,12 @@ impl ReadWriteLaneFsAdapter { } #[async_trait::async_trait] -impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { +impl agentfs_core::FileSystem for ReadWriteLaneFsAdapter { async fn lookup( &self, parent_ino: i64, name: &str, - ) -> std::result::Result, agentfs_sdk::error::Error> { + ) -> std::result::Result, agentfs_core::error::Error> { let _lane = self.lock_read_fs().await; self.inner.lookup(parent_ino, name).await } @@ -161,7 +193,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { async fn getattr( &self, ino: i64, - ) -> std::result::Result, agentfs_sdk::error::Error> { + ) -> std::result::Result, agentfs_core::error::Error> { let _lane = self.lock_read_fs().await; self.inner.getattr(ino).await } @@ -169,7 +201,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { async fn readlink( &self, ino: i64, - ) -> std::result::Result, agentfs_sdk::error::Error> { + ) -> std::result::Result, agentfs_core::error::Error> { let _lane = self.lock_read_fs().await; self.inner.readlink(ino).await } @@ -177,7 +209,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { async fn readdir( &self, ino: i64, - ) -> std::result::Result>, agentfs_sdk::error::Error> { + ) -> std::result::Result>, agentfs_core::error::Error> { let _lane = self.lock_read_fs().await; self.inner.readdir(ino).await } @@ -185,7 +217,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { async fn readdir_plus( &self, ino: i64, - ) -> std::result::Result>, agentfs_sdk::error::Error> { + ) -> std::result::Result>, agentfs_core::error::Error> { let _lane = self.lock_read_fs().await; self.inner.readdir_plus(ino).await } @@ -194,7 +226,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { &self, ino: i64, mode: u32, - ) -> std::result::Result<(), agentfs_sdk::error::Error> { + ) -> std::result::Result<(), agentfs_core::error::Error> { let _lane = self.lock_write_fs().await; self.inner.chmod(ino, mode).await } @@ -204,7 +236,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { ino: i64, uid: Option, gid: Option, - ) -> std::result::Result<(), agentfs_sdk::error::Error> { + ) -> std::result::Result<(), agentfs_core::error::Error> { let _lane = self.lock_write_fs().await; self.inner.chown(ino, uid, gid).await } @@ -212,9 +244,9 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { async fn utimens( &self, ino: i64, - atime: agentfs_sdk::TimeChange, - mtime: agentfs_sdk::TimeChange, - ) -> std::result::Result<(), agentfs_sdk::error::Error> { + atime: agentfs_core::TimeChange, + mtime: agentfs_core::TimeChange, + ) -> std::result::Result<(), agentfs_core::error::Error> { let _lane = self.lock_write_fs().await; self.inner.utimens(ino, atime, mtime).await } @@ -223,7 +255,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { &self, ino: i64, flags: i32, - ) -> std::result::Result { + ) -> std::result::Result { match classify_open(flags) { FuseFsOperationClass::PureRead => { let _lane = self.lock_read_fs().await; @@ -240,7 +272,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { &self, ino: i64, flags: i32, - ) -> std::result::Result, agentfs_sdk::error::Error> { + ) -> std::result::Result, agentfs_core::error::Error> { match classify_open(flags) { FuseFsOperationClass::PureRead => { let _lane = self.lock_read_fs().await; @@ -253,6 +285,14 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { } } + fn delta_keep_cache_fast_path(&self) -> bool { + self.inner.delta_keep_cache_fast_path() + } + + fn kernel_cache_policy(&self, ino: i64) -> agentfs_core::fs::KernelCachePolicy { + self.inner.kernel_cache_policy(ino) + } + async fn mkdir( &self, parent_ino: i64, @@ -260,7 +300,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { mode: u32, uid: u32, gid: u32, - ) -> std::result::Result { + ) -> std::result::Result { let _lane = self.lock_write_fs().await; self.inner.mkdir(parent_ino, name, mode, uid, gid).await } @@ -272,8 +312,10 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { mode: u32, uid: u32, gid: u32, - ) -> std::result::Result<(agentfs_sdk::Stats, agentfs_sdk::BoxedFile), agentfs_sdk::error::Error> - { + ) -> std::result::Result< + (agentfs_core::Stats, agentfs_core::BoxedFile), + agentfs_core::error::Error, + > { let _lane = self.lock_write_fs().await; self.inner .create_file(parent_ino, name, mode, uid, gid) @@ -288,7 +330,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { rdev: u64, uid: u32, gid: u32, - ) -> std::result::Result { + ) -> std::result::Result { let _lane = self.lock_write_fs().await; self.inner .mknod(parent_ino, name, mode, rdev, uid, gid) @@ -302,7 +344,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { target: &str, uid: u32, gid: u32, - ) -> std::result::Result { + ) -> std::result::Result { let _lane = self.lock_write_fs().await; self.inner.symlink(parent_ino, name, target, uid, gid).await } @@ -311,7 +353,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { &self, parent_ino: i64, name: &str, - ) -> std::result::Result<(), agentfs_sdk::error::Error> { + ) -> std::result::Result<(), agentfs_core::error::Error> { let _lane = self.lock_write_fs().await; self.inner.unlink(parent_ino, name).await } @@ -320,7 +362,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { &self, parent_ino: i64, name: &str, - ) -> std::result::Result<(), agentfs_sdk::error::Error> { + ) -> std::result::Result<(), agentfs_core::error::Error> { let _lane = self.lock_write_fs().await; self.inner.rmdir(parent_ino, name).await } @@ -330,7 +372,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { ino: i64, newparent_ino: i64, newname: &str, - ) -> std::result::Result { + ) -> std::result::Result { let _lane = self.lock_write_fs().await; self.inner.link(ino, newparent_ino, newname).await } @@ -341,7 +383,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { oldname: &str, newparent_ino: i64, newname: &str, - ) -> std::result::Result<(), agentfs_sdk::error::Error> { + ) -> std::result::Result<(), agentfs_core::error::Error> { let _lane = self.lock_write_fs().await; self.inner .rename(oldparent_ino, oldname, newparent_ino, newname) @@ -350,7 +392,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { async fn statfs( &self, - ) -> std::result::Result { + ) -> std::result::Result { let _lane = self.lock_read_fs().await; self.inner.statfs().await } @@ -358,17 +400,17 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { async fn drain_inode_writes( &self, ino: i64, - ) -> std::result::Result<(), agentfs_sdk::error::Error> { + ) -> std::result::Result<(), agentfs_core::error::Error> { let _lane = self.lock_write_fs().await; self.inner.drain_inode_writes(ino).await } - async fn drain_all(&self) -> std::result::Result<(), agentfs_sdk::error::Error> { + async fn drain_all(&self) -> std::result::Result<(), agentfs_core::error::Error> { let _lane = self.lock_write_fs().await; self.inner.drain_all().await } - async fn finalize(&self) -> std::result::Result<(), agentfs_sdk::error::Error> { + async fn finalize(&self) -> std::result::Result<(), agentfs_core::error::Error> { let _lane = self.lock_write_fs().await; self.inner.finalize().await } @@ -377,7 +419,7 @@ impl agentfs_sdk::FileSystem for ReadWriteLaneFsAdapter { &self, ino: i64, nlookup: u64, - ) -> std::result::Result<(), agentfs_sdk::error::Error> { + ) -> std::result::Result<(), agentfs_core::error::Error> { let _lane = self.lock_read_fs().await; self.inner.retain_lookup(ino, nlookup).await } diff --git a/crates/agentfs-mount/src/lib.rs b/crates/agentfs-mount/src/lib.rs new file mode 100644 index 00000000..30d89c4a --- /dev/null +++ b/crates/agentfs-mount/src/lib.rs @@ -0,0 +1,567 @@ +//! Mount lifecycle infrastructure for AgentFS. +//! +//! This module provides a unified mount API that abstracts over FUSE and NFS backends. +//! The `mount_fs()` function returns a `MountHandle` whose explicit `unmount` +//! path drains and joins backend-owned work before returning. The exported +//! surface is the mount lifecycle only: `mount_fs`/`MountOpts`/`MountHandle`, +//! the `supervise` module (the one supervision path for every mount-owning +//! command), the `daemon` module, and mountpoint helpers. +//! +//! Owned invariants: +//! +//! - One mount lifecycle: every backend mount is created through `mount_fs` +//! and torn down through `MountHandle::unmount`, which joins all +//! backend-owned threads/tasks within bounded timeouts — no leaked mounts +//! or orphan sessions on the success path. +//! - One supervision path: `supervise::run_supervised*` owns +//! signal-forwarding, child-exit propagation, and +//! unmount-before-exit ordering for commands that hold a mount while a +//! child process runs. +//! - Readiness and teardown are time-bounded (`wait_for_mount`, unmount +//! timeouts), so a wedged backend surfaces as an error instead of a hang. +//! +//! # Example +//! +//! ```ignore +//! use agentfs_mount::{mount_fs, Backend, MountOpts}; +//! +//! let opts = MountOpts::new(PathBuf::from("/mnt/agent"), Backend::Fuse); +//! let handle = mount_fs(Arc::new(my_fs), opts).await?; +//! // ... use the mounted filesystem ... +//! handle.unmount().await?; +//! ``` + +pub mod daemon; +#[cfg(target_os = "linux")] +mod fuse; +mod nfs; +pub mod supervise; + +use anyhow::Result; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::time::Duration; + +/// Default timeout for mount to become ready. +const DEFAULT_MOUNT_TIMEOUT: Duration = Duration::from_secs(10); +const DEFAULT_UNMOUNT_TIMEOUT: Duration = Duration::from_secs(5); + +#[cfg(target_os = "linux")] +pub(crate) fn get_runtime() -> tokio::runtime::Runtime { + tokio::runtime::Runtime::new().expect("internal error: failed to initialize runtime") +} + +/// Mount backend type. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Backend { + /// FUSE filesystem (Linux only). + Fuse, + /// NFS over localhost. + Nfs, +} + +// Platform-specific default: FUSE on Linux, NFS elsewhere. +#[allow(clippy::derivable_impls)] +impl Default for Backend { + fn default() -> Self { + #[cfg(target_os = "linux")] + { + Backend::Fuse + } + #[cfg(not(target_os = "linux"))] + { + Backend::Nfs + } + } +} + +impl std::fmt::Display for Backend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Backend::Fuse => write!(f, "fuse"), + Backend::Nfs => write!(f, "nfs"), + } + } +} + +/// Options for mounting a filesystem. +/// +/// This struct provides a unified configuration for both FUSE and NFS backends. +/// Use `MountOpts::new()` to create default options, then customize as needed. +#[derive(Debug, Clone)] +pub struct MountOpts { + /// The mountpoint path. + pub mountpoint: PathBuf, + /// Mount backend to use. + pub backend: Backend, + /// Filesystem name shown in mount output. + pub fsname: String, + /// User ID to report for all files. + pub uid: Option, + /// Group ID to report for all files. + pub gid: Option, + /// Allow other system users to access the mount. + pub allow_other: bool, + /// Allow root to access the mount (FUSE only). + pub allow_root: bool, + /// Auto unmount when process exits (FUSE only). + pub auto_unmount: bool, + /// Use lazy unmount on cleanup. + pub lazy_unmount: bool, + /// Timeout for mount to become ready. + pub timeout: Duration, +} + +impl MountOpts { + /// Create default options for the given mountpoint and backend. + pub fn new(mountpoint: PathBuf, backend: Backend) -> Self { + Self { + mountpoint, + backend, + fsname: "agentfs".to_string(), + uid: None, + gid: None, + allow_other: false, + allow_root: false, + auto_unmount: false, + lazy_unmount: false, + timeout: DEFAULT_MOUNT_TIMEOUT, + } + } +} + +impl Default for MountOpts { + fn default() -> Self { + Self::new(PathBuf::new(), Backend::default()) + } +} + +/// Alias for the architecture-level mount specification. +pub type MountSpec = MountOpts; + +/// Options for serving AgentFS over NFS without mounting it locally. +#[derive(Debug, Clone)] +pub struct NfsServerOptions { + /// IP address or hostname to bind. + pub bind: String, + /// TCP port to bind. Use `0` to request an ephemeral port. + pub port: u32, +} + +impl NfsServerOptions { + /// Create NFS server options for the given bind host and port. + pub fn new(bind: impl Into, port: u32) -> Self { + Self { + bind: bind.into(), + port, + } + } +} + +impl Default for NfsServerOptions { + fn default() -> Self { + Self::new("127.0.0.1", 0) + } +} + +/// Handle for a standalone NFS server owned by the mount lifecycle crate. +pub struct NfsServerHandle { + inner: agentfs_nfs::ServerHandle, +} + +impl NfsServerHandle { + /// Listening address chosen by the OS. + pub fn local_addr(&self) -> std::net::SocketAddr { + self.inner.local_addr() + } + + /// Listening TCP port chosen by the OS. + pub fn local_port(&self) -> u16 { + self.inner.local_port() + } + + /// Request cooperative server shutdown. + pub fn cancel(&self) { + self.inner.cancel(); + } + + /// Whether the background server task has finished. + pub fn is_finished(&self) -> bool { + self.inner.is_finished() + } + + /// Wait for the server task to stop and surface shutdown errors. + pub async fn join(mut self) -> Result<()> { + self.inner.join().await + } +} + +/// Serve a filesystem over NFS through the mount lifecycle crate's sealed edge. +pub async fn serve_nfs( + fs: Arc, + opts: NfsServerOptions, +) -> Result { + let shutdown = tokio_util::sync::CancellationToken::new(); + let inner = agentfs_nfs::serve( + fs, + agentfs_nfs::NfsServeOptions::new(opts.bind, opts.port), + shutdown, + ) + .await?; + Ok(NfsServerHandle { inner }) +} + +/// A mounted filesystem handle. +/// +/// This handle represents an active mount. Prefer calling [`MountHandle::unmount`] +/// so the backend can join all worker tasks and surface teardown errors. Drop is +/// retained as best-effort cleanup for early-return paths. +pub struct MountHandle { + mountpoint: PathBuf, + backend: Backend, + lazy_unmount: bool, + inner: MountHandleInner, +} + +pub(crate) enum MountHandleInner { + #[cfg(target_os = "linux")] + Fuse { + session: Option, + }, + Nfs { + server_handle: Option, + }, +} + +impl MountHandle { + /// Get the mountpoint path. + pub fn mountpoint(&self) -> &Path { + &self.mountpoint + } + + /// Whether the backend-owned serving task has stopped. + pub fn is_finished(&self) -> bool { + match &self.inner { + #[cfg(target_os = "linux")] + MountHandleInner::Fuse { session } => session + .as_ref() + .map(agentfs_fuse::SessionHandle::is_finished) + .unwrap_or(true), + MountHandleInner::Nfs { server_handle } => server_handle + .as_ref() + .map(agentfs_nfs::ServerHandle::is_finished) + .unwrap_or(true), + } + } + + /// Unmount and join all backend-owned work. + /// + /// FUSE teardown requests the session unmount, joins the session thread + /// (which drains FUSE dispatch workers and uring queue threads), then + /// verifies the mountpoint is no longer mounted. NFS teardown cancels the + /// server token, unmounts the client mount, and awaits the server task so + /// acknowledged writes drain and `finalize()` runs. + pub async fn unmount(mut self) -> Result<()> { + self.unmount_inner_async().await + } + + async fn unmount_inner_async(&mut self) -> Result<()> { + let _ = std::env::set_current_dir("/"); + let mut first_error = None; + + match &mut self.inner { + #[cfg(target_os = "linux")] + MountHandleInner::Fuse { session } => { + if let Some(session) = session.as_mut() { + if let Err(error) = session.unmount() { + remember_error( + &mut first_error, + anyhow::anyhow!( + "failed to request FUSE session unmount at {}: {}", + self.mountpoint.display(), + error + ), + ); + } + } + if is_mountpoint(&self.mountpoint) { + if let Err(error) = unmount(&self.mountpoint, self.backend, self.lazy_unmount) { + remember_error(&mut first_error, error); + } + } + if let Some(session) = session.take() { + if let Err(error) = session.join() { + remember_error(&mut first_error, error); + } + } + if is_mountpoint(&self.mountpoint) { + if let Err(error) = unmount(&self.mountpoint, self.backend, self.lazy_unmount) { + remember_error(&mut first_error, error); + } + } + if is_mountpoint(&self.mountpoint) { + remember_error( + &mut first_error, + anyhow::anyhow!( + "FUSE mountpoint {} is still mounted after teardown", + self.mountpoint.display() + ), + ); + } + } + MountHandleInner::Nfs { server_handle } => { + if let Some(handle) = server_handle.as_ref() { + handle.cancel(); + } + if is_mountpoint(&self.mountpoint) { + if let Err(error) = unmount(&self.mountpoint, self.backend, self.lazy_unmount) { + remember_error(&mut first_error, error); + } + } + if let Some(mut handle) = server_handle.take() { + match tokio::time::timeout(DEFAULT_UNMOUNT_TIMEOUT, handle.join()).await { + Ok(Ok(())) => {} + Ok(Err(error)) => remember_error(&mut first_error, error), + Err(_) => { + let timeout_error = anyhow::anyhow!( + "NFS server did not stop gracefully for {} within {:?}", + self.mountpoint.display(), + DEFAULT_UNMOUNT_TIMEOUT + ); + tracing::warn!( + mountpoint = %self.mountpoint.display(), + timeout = ?DEFAULT_UNMOUNT_TIMEOUT, + "NFS server did not stop gracefully; aborting task" + ); + handle.abort(); + match tokio::time::timeout(Duration::from_secs(1), handle.join()).await + { + Ok(Ok(())) => {} + Ok(Err(error)) => tracing::warn!( + %error, + "NFS server task reported an error after abort" + ), + Err(_) => tracing::warn!( + mountpoint = %self.mountpoint.display(), + "NFS server task did not join after abort" + ), + } + remember_error(&mut first_error, timeout_error); + } + } + } + } + } + + match first_error { + Some(error) => Err(error), + None => Ok(()), + } + } + + fn unmount_inner_sync(&mut self) { + let _ = std::env::set_current_dir("/"); + + match &mut self.inner { + #[cfg(target_os = "linux")] + MountHandleInner::Fuse { session } => { + if let Some(session) = session.as_mut() { + if let Err(error) = session.unmount() { + tracing::warn!( + mountpoint = %self.mountpoint.display(), + %error, + "failed to request FUSE session unmount" + ); + } + } + if is_mountpoint(&self.mountpoint) { + if let Err(error) = unmount(&self.mountpoint, self.backend, self.lazy_unmount) { + tracing::warn!( + mountpoint = %self.mountpoint.display(), + %error, + "failed to unmount FUSE filesystem" + ); + } + } + if let Some(session) = session.take() { + if let Err(error) = session.join() { + tracing::warn!(%error, "FUSE session exited with error"); + } + } + if is_mountpoint(&self.mountpoint) { + if let Err(error) = unmount(&self.mountpoint, self.backend, self.lazy_unmount) { + tracing::warn!( + mountpoint = %self.mountpoint.display(), + %error, + "failed final FUSE unmount" + ); + } + } + if is_mountpoint(&self.mountpoint) { + tracing::warn!( + mountpoint = %self.mountpoint.display(), + "FUSE mountpoint is still mounted after teardown" + ); + } + } + MountHandleInner::Nfs { server_handle } => { + if let Some(handle) = server_handle.as_ref() { + handle.cancel(); + } + + if is_mountpoint(&self.mountpoint) { + if let Err(error) = unmount(&self.mountpoint, self.backend, self.lazy_unmount) { + tracing::warn!( + mountpoint = %self.mountpoint.display(), + %error, + "failed to unmount NFS filesystem" + ); + } + } + + if let Some(mut handle) = server_handle.take() { + let deadline = std::time::Instant::now() + DEFAULT_UNMOUNT_TIMEOUT; + while !handle.is_finished() && std::time::Instant::now() < deadline { + std::thread::sleep(Duration::from_millis(10)); + } + if !handle.is_finished() { + tracing::warn!( + mountpoint = %self.mountpoint.display(), + timeout = ?DEFAULT_UNMOUNT_TIMEOUT, + "NFS server did not stop gracefully; aborting task" + ); + handle.abort(); + } + } + } + } + } +} + +impl Drop for MountHandle { + fn drop(&mut self) { + self.unmount_inner_sync(); + } +} + +/// Unmount a filesystem at the given mountpoint. +/// +/// This function handles unmounting for both FUSE and NFS backends. +/// If `lazy` is true, uses lazy unmount which detaches immediately even if busy. +pub fn unmount(mountpoint: &Path, backend: Backend, lazy: bool) -> Result<()> { + match backend { + #[cfg(target_os = "linux")] + Backend::Fuse => fuse::unmount_fuse(mountpoint, lazy), + #[cfg(not(target_os = "linux"))] + Backend::Fuse => anyhow::bail!("FUSE is not supported on this platform"), + Backend::Nfs => nfs::unmount_nfs(mountpoint, lazy), + } +} + +/// Mount a filesystem with the given options. +/// +/// Returns a handle that automatically unmounts when dropped. +/// The filesystem must be wrapped in `Arc`. +#[cfg(target_os = "linux")] +pub async fn mount_fs( + fs: Arc, + opts: MountOpts, +) -> Result { + match opts.backend { + Backend::Fuse => fuse::mount_fuse(fs, opts), + Backend::Nfs => nfs::mount_nfs(fs, opts).await, + } +} + +/// Mount a filesystem with the given options (macOS version). +#[cfg(target_os = "macos")] +pub async fn mount_fs( + fs: Arc, + opts: MountOpts, +) -> Result { + match opts.backend { + Backend::Fuse => { + anyhow::bail!( + "FUSE mounting is not supported on macOS.\n\ + Use --backend nfs (default) instead." + ); + } + Backend::Nfs => nfs::mount_nfs(fs, opts).await, + } +} + +fn remember_error(slot: &mut Option, error: anyhow::Error) { + if slot.is_none() { + *slot = Some(error); + } +} + +/// Resolve when SIGTERM, SIGINT, or SIGHUP is delivered. +/// +/// Mount-owning commands must tear down through this rather than the default +/// signal disposition: dying without unmounting leaves a dead mount table +/// entry (ENOTCONN for every later visitor) and skips `MountHandle`'s Drop. +#[cfg(unix)] +pub async fn termination_signal() -> std::io::Result { + use tokio::signal::unix::{signal, SignalKind}; + let mut term = signal(SignalKind::terminate())?; + let mut int = signal(SignalKind::interrupt())?; + let mut hup = signal(SignalKind::hangup())?; + let signo = tokio::select! { + _ = term.recv() => libc::SIGTERM, + _ = int.recv() => libc::SIGINT, + _ = hup.recv() => libc::SIGHUP, + }; + Ok(signo) +} + +/// Resolve when SIGTERM, SIGINT, or SIGHUP is delivered. +#[cfg(unix)] +pub async fn shutdown_signal() -> std::io::Result<()> { + termination_signal().await.map(|_| ()) +} + +/// Wait for a path to become a mountpoint. +pub fn wait_for_mount(path: &Path, timeout: Duration) -> bool { + let start = std::time::Instant::now(); + let interval = Duration::from_millis(50); + + while start.elapsed() < timeout { + if is_mountpoint(path) { + return true; + } + std::thread::sleep(interval); + } + false +} + +/// Check if a path is a mountpoint by comparing device IDs with parent. +pub fn is_mountpoint(path: &Path) -> bool { + #[cfg(unix)] + { + use std::os::unix::fs::MetadataExt; + + let path_meta = match std::fs::metadata(path) { + Ok(m) => m, + Err(_) => return false, + }; + + let parent = match path.parent() { + Some(p) if !p.as_os_str().is_empty() => p, + _ => Path::new("/"), + }; + + let parent_meta = match std::fs::metadata(parent) { + Ok(m) => m, + Err(_) => return false, + }; + + path_meta.dev() != parent_meta.dev() + } + + #[cfg(not(unix))] + { + let _ = path; + false + } +} diff --git a/cli/src/mount/nfs.rs b/crates/agentfs-mount/src/nfs.rs similarity index 84% rename from cli/src/mount/nfs.rs rename to crates/agentfs-mount/src/nfs.rs index e32c95c7..cc34a839 100644 --- a/cli/src/mount/nfs.rs +++ b/crates/agentfs-mount/src/nfs.rs @@ -5,10 +5,9 @@ use std::path::Path; use std::process::Command; use std::sync::Arc; -use crate::nfs::AgentNFS; -use crate::nfsserve::tcp::NFSTcp; +use agentfs_nfs::{serve, NfsServeOptions}; -use super::{MountBackend, MountHandle, MountHandleInner, MountOpts}; +use super::{Backend, MountHandle, MountHandleInner, MountOpts}; /// Default NFS port to try (use a high port to avoid needing root). const DEFAULT_NFS_PORT: u32 = 11111; @@ -77,28 +76,21 @@ pub(super) fn unmount_nfs(mountpoint: &Path, lazy: bool) -> Result<()> { /// Internal NFS mount implementation. pub(super) async fn mount_nfs( - fs: Arc, + fs: Arc, opts: MountOpts, ) -> Result { use tokio_util::sync::CancellationToken; - let nfs = AgentNFS::new(fs); - let port = find_available_port(DEFAULT_NFS_PORT)?; - let bind_addr = format!("127.0.0.1:{}", port); - let listener = crate::nfsserve::tcp::NFSTcpListener::bind(&bind_addr, nfs) - .await - .context("Failed to bind NFS server")?; - - // CancellationToken is kept for API compatibility, but the vendored nfsserve - // doesn't support graceful shutdown. The task will be aborted on drop. let shutdown = CancellationToken::new(); - let server_handle = tokio::spawn(async move { - if let Err(e) = listener.handle_forever().await { - eprintln!("NFS server error: {}", e); - } - }); + let server_handle = serve( + fs, + NfsServeOptions::new("127.0.0.1", port), + shutdown.clone(), + ) + .await + .context("Failed to bind NFS server")?; tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; @@ -106,11 +98,10 @@ pub(super) async fn mount_nfs( Ok(MountHandle { mountpoint: opts.mountpoint, - backend: MountBackend::Nfs, + backend: Backend::Nfs, lazy_unmount: opts.lazy_unmount, inner: MountHandleInner::Nfs { - shutdown, - _server_handle: server_handle, + server_handle: Some(server_handle), }, }) } diff --git a/crates/agentfs-mount/src/supervise.rs b/crates/agentfs-mount/src/supervise.rs new file mode 100644 index 00000000..76370745 --- /dev/null +++ b/crates/agentfs-mount/src/supervise.rs @@ -0,0 +1,822 @@ +//! Signal-aware child supervision helpers for mount-owning commands. +//! +//! Children get a parent-death signal on Linux, are placed in their own +//! process group when requested, and interrupted workloads get a bounded +//! TERM-then-KILL teardown before the mount is unmounted. + +#[cfg(test)] +use anyhow::Context; +use anyhow::Result; +use std::future::Future; +#[cfg(test)] +use std::path::Path; +#[cfg(test)] +use std::pin::Pin; +use std::process::ExitStatus; +use std::time::Duration; + +use crate::MountHandle; + +/// Outcome of a supervised command. +pub enum ChildOutcome { + Exited(ExitStatus), + Interrupted(i32), +} + +/// Outcome of supervised async work that is not itself a child process. +pub enum SupervisedTaskOutcome { + Completed(T), + Interrupted(i32), +} + +/// Options for mount-owning child supervision. +#[derive(Debug, Clone, Copy)] +pub struct SuperviseOpts { + /// Time to wait after TERM before escalating to KILL. + pub term_grace: Duration, + /// Put the child in a new process group and signal the whole group. + pub kill_process_group: bool, +} + +impl Default for SuperviseOpts { + fn default() -> Self { + Self { + term_grace: Duration::from_secs(5), + kill_process_group: true, + } + } +} + +/// Run a command with a mounted filesystem and always unmount afterward. +pub async fn run_supervised( + handle: MountHandle, + command: tokio::process::Command, +) -> Result { + run_supervised_with_opts(handle, command, SuperviseOpts::default()).await +} + +/// Run a command with custom supervision options and always unmount afterward. +pub async fn run_supervised_with_opts( + handle: MountHandle, + mut command: tokio::process::Command, + opts: SuperviseOpts, +) -> Result { + configure_supervised_command(&mut command, opts); + + let mut child = match command.spawn() { + Ok(child) => child, + Err(error) => return Err(spawn_failure_error(error, handle.unmount().await)), + }; + + let status = wait_for_supervised_child(&mut child, opts).await; + let unmount = handle.unmount().await; + unmount?; + status +} + +/// Run arbitrary async work with a mounted filesystem and always unmount afterward. +/// +/// This covers mount-owning flows whose lifetime is not represented by one +/// child process, such as `agentfs clone`'s mount plus bulk-import pipeline. +pub async fn run_supervised_task( + handle: MountHandle, + task: F, +) -> Result> +where + F: Future>, +{ + let outcome = tokio::select! { + result = task => result.map(SupervisedTaskOutcome::Completed), + signal = crate::termination_signal() => { + signal + .map(SupervisedTaskOutcome::Interrupted) + .map_err(anyhow::Error::from) + }, + }; + + let unmount = handle.unmount().await; + unmount?; + outcome +} + +/// Wait for a foreground mount to end, then unmount through the shared path. +pub async fn supervise_mount(handle: MountHandle) -> Result<()> { + tokio::select! { + result = crate::shutdown_signal() => result.map_err(anyhow::Error::from), + _ = async { + loop { + if handle.is_finished() || !crate::is_mountpoint(handle.mountpoint()) { + break; + } + tokio::time::sleep(Duration::from_millis(250)).await; + } + } => Ok(()), + }?; + handle.unmount().await +} + +/// Supervise a command without owning a mount. +pub async fn supervise_command(mut command: tokio::process::Command) -> Result { + let opts = SuperviseOpts::default(); + configure_supervised_command(&mut command, opts); + + let mut child = command.spawn()?; + let signo = tokio::select! { + status = child.wait() => { + let status = status?; + if let Some(signo) = signal_from_status(&status) { + return Ok(ChildOutcome::Interrupted(signo)); + } + return Ok(ChildOutcome::Exited(status)); + }, + signal = crate::termination_signal() => signal?, + }; + + terminate_child(&mut child, opts.kill_process_group, signo); + wait_or_kill(&mut child, opts).await?; + Ok(ChildOutcome::Interrupted(signo)) +} + +/// Hooks for supervising a forked child. +#[cfg(unix)] +#[derive(Default)] +pub struct SuperviseHooks { + profile_checkpoint: Option>, +} + +#[cfg(unix)] +impl SuperviseHooks { + pub fn with_profile_checkpoint(hook: impl FnMut() + Send + 'static) -> Self { + Self { + profile_checkpoint: Some(Box::new(hook)), + } + } +} + +/// Run an already-forked child with a mounted filesystem and always unmount afterward. +#[cfg(unix)] +pub async fn run_supervised_pid_with_hooks( + handle: MountHandle, + pid: libc::pid_t, + opts: SuperviseOpts, + hooks: SuperviseHooks, +) -> Result { + let status = supervise_pid_with_hooks(pid, opts, hooks).await; + let unmount = handle.unmount().await; + unmount?; + status +} + +/// Run an already-forked child with default supervision and a mounted filesystem. +#[cfg(unix)] +pub async fn run_supervised_pid(handle: MountHandle, pid: libc::pid_t) -> Result { + run_supervised_pid_with_hooks( + handle, + pid, + SuperviseOpts::default(), + SuperviseHooks::default(), + ) + .await +} + +/// Supervise an already-forked child without owning a mount. +#[cfg(unix)] +pub async fn supervise_pid_with_hooks( + pid: libc::pid_t, + opts: SuperviseOpts, + mut hooks: SuperviseHooks, +) -> Result { + use tokio::signal::unix::{signal, Signal, SignalKind}; + + let mut term = signal(SignalKind::terminate())?; + let mut int = signal(SignalKind::interrupt())?; + let mut hup = signal(SignalKind::hangup())?; + let mut child = signal(SignalKind::child())?; + let mut usr1 = if hooks.profile_checkpoint.is_some() { + Some(signal(SignalKind::user_defined1())?) + } else { + None + }; + + loop { + if let Some(status) = try_wait_pid(pid)? { + return Ok(status); + } + + tokio::select! { + _ = term.recv() => return terminate_pid_and_wait(pid, opts, libc::SIGTERM).await, + _ = int.recv() => return terminate_pid_and_wait(pid, opts, libc::SIGINT).await, + _ = hup.recv() => return terminate_pid_and_wait(pid, opts, libc::SIGHUP).await, + _ = child.recv() => {}, + _ = recv_optional_signal(&mut usr1) => { + if let Some(checkpoint) = hooks.profile_checkpoint.as_mut() { + checkpoint(); + } + }, + } + } + + async fn recv_optional_signal(signal: &mut Option) { + match signal { + Some(signal) => { + let _ = signal.recv().await; + } + None => std::future::pending::<()>().await, + } + } +} + +/// Prepare a child created by a command-specific `fork()` for shared supervision. +#[cfg(unix)] +pub fn prepare_forked_child(kill_process_group: bool) -> std::io::Result<()> { + supervised_child_pre_exec(kill_process_group) +} + +/// Convert a child status to the process exit code used by shell-like commands. +#[cfg(unix)] +pub fn exit_code_for_status(status: ExitStatus) -> i32 { + signal_from_status(&status) + .map(|signal| 128 + signal) + .or_else(|| status.code()) + .unwrap_or(1) +} + +#[cfg(not(unix))] +pub fn exit_code_for_status(status: ExitStatus) -> i32 { + status.code().unwrap_or(1) +} + +/// Build the error for a spawn failure followed by the teardown unmount. +/// +/// The spawn `io::Error` must stay the downcast root even when the unmount +/// also fails: callers map it to the 127/126 shell conventions via +/// [`exit_code_for_spawn_error`], and an unmount error that shadowed it +/// would reroute a missing-command run to the generic error reporter. The +/// unmount failure rides along as context instead. +fn spawn_failure_error(spawn_error: std::io::Error, unmount_result: Result<()>) -> anyhow::Error { + let error = anyhow::Error::from(spawn_error); + match unmount_result { + Ok(()) => error, + Err(unmount_error) => error.context(format!( + "unmount after spawn failure also failed: {unmount_error:#}" + )), + } +} + +/// Map a spawn/exec failure to the shell command-not-found conventions: +/// 127 for a missing command, 126 for one that is found but not executable. +pub fn exit_code_for_spawn_error(error: &std::io::Error) -> Option { + match error.kind() { + std::io::ErrorKind::NotFound => Some(127), + std::io::ErrorKind::PermissionDenied => Some(126), + _ => None, + } +} + +#[cfg(test)] +pub type ShutdownFuture<'a> = Pin> + Send + 'a>>; + +#[cfg(test)] +pub trait MountedCommandBackend { + fn mountpoint(&self) -> &Path; + fn unmount(&mut self) -> Result<()>; + fn shutdown_server(&mut self) -> ShutdownFuture<'_>; + fn remove_mountpoint(&mut self) -> Result<()>; +} + +#[cfg(test)] +pub async fn supervise_mounted_command( + command: tokio::process::Command, + mut backend: B, +) -> Result +where + B: MountedCommandBackend, +{ + let outcome = supervise_command(command).await; + let cleanup = cleanup_mounted_backend(&mut backend).await; + cleanup?; + outcome +} + +#[cfg(test)] +async fn cleanup_mounted_backend(backend: &mut B) -> Result<()> +where + B: MountedCommandBackend, +{ + let mountpoint = backend.mountpoint().display().to_string(); + let unmount = backend + .unmount() + .with_context(|| format!("failed to unmount Darwin/NFS mount {mountpoint}")); + let shutdown = backend + .shutdown_server() + .await + .with_context(|| format!("failed to shut down Darwin/NFS server for {mountpoint}")); + let remove = backend + .remove_mountpoint() + .with_context(|| format!("failed to remove Darwin/NFS mountpoint {mountpoint}")); + + unmount?; + shutdown?; + remove?; + Ok(()) +} + +#[cfg(unix)] +fn signal_from_status(status: &ExitStatus) -> Option { + use std::os::unix::process::ExitStatusExt; + + status.signal() +} + +#[cfg(not(unix))] +fn signal_from_status(_status: &ExitStatus) -> Option { + None +} + +async fn wait_for_supervised_child( + child: &mut tokio::process::Child, + opts: SuperviseOpts, +) -> Result { + let signo = tokio::select! { + status = child.wait() => return Ok(status?), + signal = crate::termination_signal() => signal?, + }; + + terminate_child(child, opts.kill_process_group, signo); + let status = wait_or_kill(child, opts).await?; + if let Some(signal) = signal_from_status(&status) { + tracing_signal_teardown(signo, signal); + } + Ok(interrupted_status(signo)) +} + +async fn wait_or_kill( + child: &mut tokio::process::Child, + opts: SuperviseOpts, +) -> Result { + match tokio::time::timeout(opts.term_grace, child.wait()).await { + Ok(status) => Ok(status?), + Err(_) => { + terminate_child(child, opts.kill_process_group, libc::SIGKILL); + Ok(child.wait().await?) + } + } +} + +#[cfg(unix)] +async fn terminate_pid_and_wait( + pid: libc::pid_t, + opts: SuperviseOpts, + signal: i32, +) -> Result { + terminate_pid(pid, opts.kill_process_group, signal); + let status = match tokio::time::timeout(opts.term_grace, wait_for_pid_exit(pid)).await { + Ok(status) => status, + Err(_) => { + terminate_pid(pid, opts.kill_process_group, libc::SIGKILL); + wait_for_pid_exit(pid).await + } + }?; + if let Some(child_signal) = signal_from_status(&status) { + tracing_signal_teardown(signal, child_signal); + } + Ok(interrupted_status(signal)) +} + +#[cfg(unix)] +async fn wait_for_pid_exit(pid: libc::pid_t) -> Result { + let mut child = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::child())?; + loop { + if let Some(status) = try_wait_pid(pid)? { + return Ok(status); + } + let _ = child.recv().await; + } +} + +#[cfg(unix)] +fn interrupted_status(signal: i32) -> ExitStatus { + use std::os::unix::process::ExitStatusExt; + + ExitStatus::from_raw(signal) +} + +#[cfg(unix)] +fn try_wait_pid(pid: libc::pid_t) -> Result> { + use std::os::unix::process::ExitStatusExt; + + let mut status: libc::c_int = 0; + let rc = unsafe { libc::waitpid(pid, &mut status, libc::WNOHANG) }; + if rc == pid { + return Ok(Some(ExitStatus::from_raw(status))); + } + if rc == 0 { + return Ok(None); + } + + let error = std::io::Error::last_os_error(); + if error.raw_os_error() == Some(libc::EINTR) { + return Ok(None); + } + Err(error.into()) +} + +fn terminate_child(child: &mut tokio::process::Child, kill_process_group: bool, signal: i32) { + let Some(pid) = child.id() else { + return; + }; + terminate_pid(pid as i32, kill_process_group, signal); +} + +#[cfg(unix)] +fn terminate_pid(pid: libc::pid_t, kill_process_group: bool, signal: i32) { + let rc = if kill_process_group { + unsafe { libc::killpg(pid, signal) } + } else { + unsafe { libc::kill(pid, signal) } + }; + if rc != 0 { + let error = std::io::Error::last_os_error(); + if error.raw_os_error() != Some(libc::ESRCH) { + tracing::warn!(pid, %error, "failed to signal supervised child"); + } + } +} + +fn configure_supervised_command(command: &mut tokio::process::Command, opts: SuperviseOpts) { + unsafe { + command.pre_exec(move || supervised_child_pre_exec(opts.kill_process_group)); + } +} + +fn tracing_signal_teardown(parent_signal: i32, child_signal: i32) { + tracing::debug!( + parent_signal, + child_signal, + "supervised child exited after process-group teardown" + ); +} + +#[cfg(target_os = "linux")] +pub fn set_parent_death_signal(command: &mut tokio::process::Command) { + unsafe { + command.pre_exec(move || supervised_child_pre_exec(false)); + } +} + +#[cfg(not(target_os = "linux"))] +pub fn set_parent_death_signal(_command: &mut tokio::process::Command) {} + +#[cfg(target_os = "linux")] +pub fn set_parent_death_signal_std(command: &mut std::process::Command) { + use std::os::unix::process::CommandExt; + + unsafe { + command.pre_exec(move || supervised_child_pre_exec(false)); + } +} + +#[cfg(not(target_os = "linux"))] +pub fn set_parent_death_signal_std(_command: &mut std::process::Command) {} + +#[cfg(unix)] +fn supervised_child_pre_exec(kill_process_group: bool) -> std::io::Result<()> { + #[cfg(target_os = "linux")] + parent_death_signal_hook()?; + + if kill_process_group && unsafe { libc::setsid() } == -1 { + return Err(std::io::Error::last_os_error()); + } + + Ok(()) +} + +#[cfg(target_os = "linux")] +fn parent_death_signal_hook() -> std::io::Result<()> { + if unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) } != 0 { + return Err(std::io::Error::last_os_error()); + } + // The parent may have died between fork and prctl. + if unsafe { libc::getppid() } == 1 { + unsafe { libc::raise(libc::SIGKILL) }; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::Context; + use std::sync::{Arc, Mutex}; + use std::time::{Duration, Instant}; + + #[tokio::test] + async fn fake_darwin_supervision_path_cleans_up_after_sigint() { + let temp = tempfile::tempdir().expect("tempdir"); + let mountpoint = temp.path().join("darwin-nfs-mount"); + std::fs::create_dir(&mountpoint).expect("create fake mountpoint"); + let child_pid_path = temp.path().join("child.pid"); + let child_ready_path = temp.path().join("child.ready"); + + let (backend, registry) = FakeDarwinMountBackend::mounted(mountpoint.clone()); + + let mut command = tokio::process::Command::new("sh"); + command + .arg("-c") + .arg( + "printf '%s\\n' \"$$\" > \"$1\"; : > \"$2\"; \ + while :; do sleep 1; done", + ) + .arg("agentfs-darwin-supervision-child") + .arg(&child_pid_path) + .arg(&child_ready_path) + .current_dir(&mountpoint); + + let signal_ready_path = child_ready_path.clone(); + let signaler = tokio::spawn(async move { + wait_for_path(&signal_ready_path).await; + tokio::time::sleep(Duration::from_millis(100)).await; + let rc = unsafe { libc::kill(libc::getpid(), libc::SIGINT) }; + assert_eq!(rc, 0, "failed to deliver real SIGINT to test process"); + }); + + let outcome = tokio::time::timeout( + Duration::from_secs(10), + supervise_mounted_command(command, backend), + ) + .await + .expect("Darwin/NFS supervision cleanup exceeded 10s") + .expect("Darwin/NFS supervision failed"); + + signaler.await.expect("signal task panicked"); + + match outcome { + ChildOutcome::Interrupted(signo) => assert_eq!(signo, libc::SIGINT), + ChildOutcome::Exited(status) => { + panic!("child exited without SIGINT supervision: {status}") + } + } + + let child_pid = std::fs::read_to_string(&child_pid_path) + .expect("child pid file") + .trim() + .parse::() + .expect("child pid"); + assert!( + !process_exists(child_pid), + "supervised child process {child_pid} survived cleanup" + ); + + let state = registry.snapshot(); + assert_eq!(state.unmount_calls, 1, "unmount must be called once"); + assert_eq!( + state.shutdown_calls, 1, + "NFS server shutdown must be called once" + ); + assert_eq!( + state.remove_mountpoint_calls, 1, + "mountpoint removal must be called once" + ); + assert!( + state.unmount_elapsed.expect("unmount duration recorded") < Duration::from_secs(10), + "fake unmount exceeded the 10s contract bound" + ); + assert!(!state.mounted, "fake mount registry still marks mount live"); + assert!( + !state.nfs_server_running, + "fake NFS server registry still marks server live" + ); + assert!( + !mountpoint.exists(), + "fake mountpoint should be removed after cleanup" + ); + + println!( + "Darwin/NFS supervision path cleaned after real SIGINT: \ + child_pid={child_pid}, unmount_elapsed={:?}, registry={state:?}", + state.unmount_elapsed + ); + } + + #[tokio::test] + async fn process_group_teardown_kills_grandchild() { + let temp = tempfile::tempdir().expect("tempdir"); + let child_pid_path = temp.path().join("child.pid"); + let grandchild_pid_path = temp.path().join("grandchild.pid"); + let ready_path = temp.path().join("ready"); + + let mut command = tokio::process::Command::new("sh"); + command + .arg("-c") + .arg( + "printf '%s\\n' \"$$\" > \"$1\"; \ + (while :; do sleep 1; done) & \ + printf '%s\\n' \"$!\" > \"$2\"; \ + : > \"$3\"; \ + wait", + ) + .arg("agentfs-supervise-group") + .arg(&child_pid_path) + .arg(&grandchild_pid_path) + .arg(&ready_path); + + let opts = SuperviseOpts { + term_grace: Duration::from_secs(2), + kill_process_group: true, + }; + configure_supervised_command(&mut command, opts); + let mut child = command.spawn().expect("spawn supervised shell"); + wait_for_path(&ready_path).await; + + let child_pid = read_pid(&child_pid_path); + let grandchild_pid = read_pid(&grandchild_pid_path); + assert!(process_exists(child_pid), "child should be running"); + assert!( + process_exists(grandchild_pid), + "grandchild should be running before process-group teardown" + ); + + terminate_child(&mut child, opts.kill_process_group, libc::SIGTERM); + wait_or_kill(&mut child, opts) + .await + .expect("wait for supervised process-group teardown"); + wait_for_process_exit(grandchild_pid).await; + + assert!( + !process_exists(child_pid), + "child process {child_pid} survived process-group teardown" + ); + assert!( + !process_exists(grandchild_pid), + "grandchild process {grandchild_pid} survived process-group teardown" + ); + } + + #[test] + fn parent_signal_status_preserves_interrupted_exit_code() { + let status = interrupted_status(libc::SIGTERM); + assert_eq!( + exit_code_for_status(status), + 128 + libc::SIGTERM, + "parent-directed teardown reports the parent signal even if the child handles TERM" + ); + } + + #[derive(Debug, Clone, Default)] + struct FakeMountState { + mounted: bool, + nfs_server_running: bool, + unmount_calls: usize, + shutdown_calls: usize, + remove_mountpoint_calls: usize, + unmount_elapsed: Option, + } + + #[derive(Clone)] + struct FakeMountRegistry { + state: Arc>, + } + + impl FakeMountRegistry { + fn snapshot(&self) -> FakeMountState { + self.state.lock().expect("fake registry poisoned").clone() + } + } + + struct FakeDarwinMountBackend { + mountpoint: std::path::PathBuf, + registry: FakeMountRegistry, + } + + impl FakeDarwinMountBackend { + fn mounted(mountpoint: std::path::PathBuf) -> (Self, FakeMountRegistry) { + let registry = FakeMountRegistry { + state: Arc::new(Mutex::new(FakeMountState { + mounted: true, + nfs_server_running: true, + ..FakeMountState::default() + })), + }; + ( + Self { + mountpoint, + registry: registry.clone(), + }, + registry, + ) + } + } + + impl MountedCommandBackend for FakeDarwinMountBackend { + fn mountpoint(&self) -> &Path { + &self.mountpoint + } + + fn unmount(&mut self) -> Result<()> { + let started = Instant::now(); + let mut state = self.registry.state.lock().expect("fake registry poisoned"); + state.unmount_calls += 1; + state.mounted = false; + state.unmount_elapsed = Some(started.elapsed()); + Ok(()) + } + + fn shutdown_server(&mut self) -> ShutdownFuture<'_> { + let registry = self.registry.clone(); + Box::pin(async move { + let mut state = registry.state.lock().expect("fake registry poisoned"); + state.shutdown_calls += 1; + state.nfs_server_running = false; + Ok(()) + }) + } + + fn remove_mountpoint(&mut self) -> Result<()> { + std::fs::remove_dir(&self.mountpoint) + .with_context(|| format!("remove fake mountpoint {}", self.mountpoint.display()))?; + let mut state = self.registry.state.lock().expect("fake registry poisoned"); + state.remove_mountpoint_calls += 1; + Ok(()) + } + } + + async fn wait_for_path(path: &Path) { + tokio::time::timeout(Duration::from_secs(5), async { + loop { + if path.exists() { + break; + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await + .expect("child workload did not become ready"); + } + + async fn wait_for_process_exit(pid: i32) { + tokio::time::timeout(Duration::from_secs(5), async move { + while process_exists(pid) { + tokio::time::sleep(Duration::from_millis(20)).await; + } + }) + .await + .unwrap_or_else(|_| panic!("process {pid} did not exit within timeout")); + } + + fn read_pid(path: &Path) -> i32 { + std::fs::read_to_string(path) + .unwrap_or_else(|error| panic!("read pid file {}: {error}", path.display())) + .trim() + .parse::() + .unwrap_or_else(|error| panic!("parse pid file {}: {error}", path.display())) + } + + fn process_exists(pid: i32) -> bool { + if unsafe { libc::kill(pid, 0) } == 0 { + return true; + } + std::io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH) + } + + #[test] + fn spawn_error_survives_a_failing_unmount() { + let spawn = std::io::Error::from_raw_os_error(libc::ENOENT); + let error = spawn_failure_error(spawn, Err(anyhow::anyhow!("unmount blew up"))); + + let io_error = error + .downcast_ref::() + .expect("spawn io::Error must stay downcastable through the unmount context"); + assert_eq!( + exit_code_for_spawn_error(io_error), + Some(127), + "double fault must still map a missing command to 127" + ); + assert!( + format!("{error:#}").contains("unmount blew up"), + "the unmount failure must be preserved as context: {error:#}" + ); + } + + #[test] + fn spawn_error_passes_through_when_unmount_succeeds() { + let spawn = std::io::Error::from_raw_os_error(libc::EACCES); + let error = spawn_failure_error(spawn, Ok(())); + + let io_error = error + .downcast_ref::() + .expect("spawn io::Error must be the error root"); + assert_eq!(exit_code_for_spawn_error(io_error), Some(126)); + } + + #[test] + fn spawn_error_maps_to_command_not_found_conventions() { + let missing = std::io::Error::from_raw_os_error(libc::ENOENT); + assert_eq!(exit_code_for_spawn_error(&missing), Some(127)); + + let not_executable = std::io::Error::from_raw_os_error(libc::EACCES); + assert_eq!(exit_code_for_spawn_error(¬_executable), Some(126)); + + let unrelated = std::io::Error::from_raw_os_error(libc::EMFILE); + assert_eq!(exit_code_for_spawn_error(&unrelated), None); + } +} diff --git a/crates/agentfs-nfs/Cargo.toml b/crates/agentfs-nfs/Cargo.toml new file mode 100644 index 00000000..8c2187e2 --- /dev/null +++ b/crates/agentfs-nfs/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "agentfs-nfs" +version.workspace = true +edition.workspace = true +description = "NFS adapter for AgentFS" +license.workspace = true + +[lints] +workspace = true + +[dependencies] +agentfs-core = { workspace = true } +anyhow = { workspace = true } +async-trait = { workspace = true } +byteorder = { workspace = true } +filetime = { workspace = true } +libc = { workspace = true } +num-derive = { workspace = true } +num-traits = { workspace = true } +smallvec = { workspace = true } +tokio = { workspace = true } +tokio-util = { workspace = true } +tracing = { workspace = true } +uuid = { workspace = true } + +[dev-dependencies] +tempfile = { workspace = true } diff --git a/cli/src/nfs.rs b/crates/agentfs-nfs/src/adapter.rs similarity index 71% rename from cli/src/nfs.rs rename to crates/agentfs-nfs/src/adapter.rs index c00b80e2..c3bbb1ce 100644 --- a/cli/src/nfs.rs +++ b/crates/agentfs-nfs/src/adapter.rs @@ -4,20 +4,18 @@ //! FileSystem trait, enabling systems to mount AgentFS via NFS without requiring //! FUSE or other system extensions. -use std::collections::HashMap; -use std::sync::{Arc, Mutex as StdMutex}; +use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; -use libc::{O_RDONLY, O_RDWR}; - -use crate::nfsserve::nfs::{ +use crate::server::nfs::{ fattr3, fileid3, filename3, ftype3, nfs_fh3, nfspath3, nfsstat3, nfstime3, sattr3, set_atime, set_gid3, set_mode3, set_mtime, set_size3, set_uid3, specdata3, }; -use crate::nfsserve::vfs::{auth_unix, DirEntry, NFSFileSystem, ReadDirResult, VFSCapabilities}; -use agentfs_sdk::error::Error as SdkError; -use agentfs_sdk::filesystem::FsError; -use agentfs_sdk::{ +use crate::server::vfs::{auth_unix, DirEntry, NFSFileSystem, ReadDirResult}; +use agentfs_core::error::Error as SdkError; +use agentfs_core::fs::FsError; +use agentfs_core::semantics::{AckDurability, Authority, Semantics}; +use agentfs_core::{ FileSystem, Stats, TimeChange, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, }; @@ -29,7 +27,6 @@ const ROOT_INO: fileid3 = 1; const WRITE_HANDLE_MAGIC: &[u8; 8] = b"AFSWRIT\0"; const PLAIN_HANDLE_LEN: usize = 16; const WRITE_HANDLE_LEN: usize = 32; -const MAX_WRITE_HANDLE_TOKENS: usize = 16384; /// Convert a fileid3 to a filesystem inode number. fn id_to_fs_ino(id: fileid3) -> i64 { @@ -54,6 +51,7 @@ fn error_to_nfsstat(e: SdkError) -> nfsstat3 { FsError::NotADirectory => nfsstat3::NFS3ERR_NOTDIR, FsError::IsADirectory => nfsstat3::NFS3ERR_ISDIR, FsError::NameTooLong => nfsstat3::NFS3ERR_NAMETOOLONG, + FsError::BadCookie => nfsstat3::NFS3ERR_BAD_COOKIE, FsError::RootOperation => nfsstat3::NFS3ERR_ACCES, _ => nfsstat3::NFS3ERR_IO, }, @@ -63,13 +61,13 @@ fn error_to_nfsstat(e: SdkError) -> nfsstat3 { } /// NFS adapter that wraps an AgentFS FileSystem. -pub struct AgentNFS { +pub(crate) struct AgentNFS { /// The underlying concurrency-safe filesystem. fs: Arc, + /// Shared semantics contract for durability and coherent attrs. + semantics: Semantics, /// Server-local generation number embedded in opaque file handles. fh_generation: u64, - /// CREATE-returned file-handle tokens that retain open-time write authority. - write_handle_tokens: StdMutex>, } impl AgentNFS { @@ -80,9 +78,9 @@ impl AgentNFS { .unwrap_or_default(); let seed = (now.as_secs() << 32) ^ u64::from(now.subsec_nanos()); AgentNFS { + semantics: Semantics::new(fs.clone()), fs, fh_generation: seed, - write_handle_tokens: StdMutex::new(HashMap::new()), } } @@ -180,27 +178,18 @@ impl NFSFileSystem for AgentNFS { ROOT_INO } - fn capabilities(&self) -> VFSCapabilities { - VFSCapabilities::ReadWrite - } - fn id_to_fh(&self, id: fileid3) -> nfs_fh3 { self.encode_plain_fh(id) } fn id_to_write_fh(&self, id: fileid3) -> nfs_fh3 { - let mut tokens = self.write_handle_tokens.lock().unwrap(); - if tokens.len() >= MAX_WRITE_HANDLE_TOKENS { - if let Some(oldest) = tokens.keys().next().copied() { - tokens.remove(&oldest); - } - } let mut token = random_write_handle_token(); - while tokens.contains_key(&token) { + while !self + .semantics + .try_grant_write_authority_with_token(id_to_fs_ino(id), token) + { token = random_write_handle_token(); } - tokens.insert(token, id); - drop(tokens); let mut ret = Vec::with_capacity(WRITE_HANDLE_LEN); ret.extend_from_slice(&self.fh_generation.to_le_bytes()); @@ -210,6 +199,19 @@ impl NFSFileSystem for AgentNFS { nfs_fh3 { data: ret } } + fn id_to_readdirplus_fh(&self, id: fileid3) -> nfs_fh3 { + if let Some(token) = self.semantics.authority_token_for_ino(id_to_fs_ino(id)) { + let mut ret = Vec::with_capacity(WRITE_HANDLE_LEN); + ret.extend_from_slice(&self.fh_generation.to_le_bytes()); + ret.extend_from_slice(&id.to_le_bytes()); + ret.extend_from_slice(WRITE_HANDLE_MAGIC); + ret.extend_from_slice(&token.to_le_bytes()); + nfs_fh3 { data: ret } + } else { + self.encode_plain_fh(id) + } + } + fn fh_has_write_authority(&self, fh: &nfs_fh3, id: fileid3) -> bool { let Ok((handle_id, Some(token))) = self.parse_fh(fh) else { return false; @@ -217,12 +219,7 @@ impl NFSFileSystem for AgentNFS { if handle_id != id { return false; } - self.write_handle_tokens - .lock() - .unwrap() - .get(&token) - .copied() - == Some(id) + self.semantics.has_write_authority(id_to_fs_ino(id), token) } fn fh_to_id(&self, fh: &nfs_fh3) -> Result { @@ -271,9 +268,9 @@ impl NFSFileSystem for AgentNFS { } async fn getattr(&self, id: fileid3) -> Result { - let fs = self.fs.clone(); - let stats = fs - .getattr(id_to_fs_ino(id)) + let stats = self + .semantics + .stat_coherent(id_to_fs_ino(id)) .await .map_err(error_to_nfsstat)? .ok_or(nfsstat3::NFS3ERR_NOENT)?; @@ -309,8 +306,16 @@ impl NFSFileSystem for AgentNFS { // Handle size change (truncate) if let set_size3::size(size) = setattr.size { - let file = fs.open(fs_ino, O_RDWR).await.map_err(error_to_nfsstat)?; - file.truncate(size).await.map_err(error_to_nfsstat)?; + let handle = self + .semantics + .open_cached(fs_ino, Authority::Write) + .await + .map_err(error_to_nfsstat)?; + handle + .file() + .truncate(size) + .await + .map_err(error_to_nfsstat)?; } // Handle atime/mtime changes (utimensat) @@ -331,8 +336,9 @@ impl NFSFileSystem for AgentNFS { } // Get updated stats - let stats = fs - .getattr(fs_ino) + let stats = self + .semantics + .stat_coherent(fs_ino) .await .map_err(error_to_nfsstat)? .ok_or(nfsstat3::NFS3ERR_NOENT)?; @@ -346,40 +352,50 @@ impl NFSFileSystem for AgentNFS { offset: u64, count: u32, ) -> Result<(Vec, bool), nfsstat3> { - let fs = self.fs.clone(); - - let file = fs - .open(id_to_fs_ino(id), O_RDONLY) + let handle = self + .semantics + .open_cached(id_to_fs_ino(id), Authority::Read) .await .map_err(|_| nfsstat3::NFS3ERR_NOENT)?; - let data = file + let data = handle + .file() .pread(offset, count as u64) .await .map_err(error_to_nfsstat)?; // Check if we've reached EOF - let stats = file.fstat().await.map_err(error_to_nfsstat)?; + let stats = handle.file().fstat().await.map_err(error_to_nfsstat)?; let eof = offset + data.len() as u64 >= stats.size as u64; Ok((data, eof)) } - async fn write(&self, id: fileid3, offset: u64, data: &[u8]) -> Result { - let fs = self.fs.clone(); - - let file = fs - .open(id_to_fs_ino(id), O_RDWR) + async fn write( + &self, + id: fileid3, + offset: u64, + data: &[u8], + durability: AckDurability, + ) -> Result<(fattr3, AckDurability), nfsstat3> { + let handle = self + .semantics + .open_cached(id_to_fs_ino(id), Authority::Write) + .await + .map_err(error_to_nfsstat)?; + let receipt = self + .semantics + .write_handle(&handle, offset, data, durability) .await .map_err(error_to_nfsstat)?; - file.pwrite(offset, data).await.map_err(error_to_nfsstat)?; - let stats = fs - .getattr(id_to_fs_ino(id)) + let stats = self + .semantics + .stat_coherent(id_to_fs_ino(id)) .await .map_err(error_to_nfsstat)? .ok_or(nfsstat3::NFS3ERR_NOENT)?; - Ok(self.stats_to_fattr(&stats)) + Ok((self.stats_to_fattr(&stats), receipt.durability)) } async fn create( @@ -536,6 +552,7 @@ impl NFSFileSystem for AgentNFS { .await .map_err(error_to_nfsstat)?; } + self.semantics.invalidate_handles(stats.ino); Ok(()) } @@ -554,9 +571,13 @@ impl NFSFileSystem for AgentNFS { let fs = self.fs.clone(); - fs.rename(from_dir_fs_ino, from_name, to_dir_fs_ino, to_name) + let replaced_ino = fs + .rename_with_replaced_ino(from_dir_fs_ino, from_name, to_dir_fs_ino, to_name) .await .map_err(error_to_nfsstat)?; + if let Some(ino) = replaced_ino { + self.semantics.invalidate_handles(ino); + } Ok(()) } @@ -590,8 +611,8 @@ impl NFSFileSystem for AgentNFS { let fs = self.fs.clone(); - let entries = fs - .readdir_plus(dir_fs_ino) + let page = fs + .readdir_plus_after(dir_fs_ino, start_after as i64, max_entries.max(1)) .await .map_err(error_to_nfsstat)? .ok_or(nfsstat3::NFS3ERR_NOENT)?; @@ -600,38 +621,20 @@ impl NFSFileSystem for AgentNFS { let mut result = ReadDirResult { entries: Vec::new(), - end: false, + end: page.end, }; - // Find start position if start_after is specified - let mut skip = start_after > 0; - let mut skipped_count = 0; - - for entry in entries.iter() { + for entry in page.entries { let ino = entry.stats.ino as fileid3; - if skip { - if ino == start_after { - skip = false; - } - skipped_count += 1; - continue; - } - - if result.entries.len() >= max_entries { - break; - } - result.entries.push(DirEntry { fileid: ino, + cookie: entry.cookie as fileid3, name: entry.name.as_bytes().into(), attr: self.stats_to_fattr(&entry.stats), }); } - // Mark as end if we've returned all remaining entries - result.end = result.entries.len() + skipped_count >= entries.len(); - Ok(result) } @@ -670,14 +673,24 @@ impl NFSFileSystem for AgentNFS { Ok(target.into_bytes().into()) } + + async fn finalize(&self) -> anyhow::Result<()> { + self.semantics + .commit_barrier(None) + .await + .map_err(anyhow::Error::from)?; + self.semantics.finalize().await.map_err(anyhow::Error::from) + } } #[cfg(test)] mod tests { use super::*; - use crate::nfsserve::vfs::NFSFileSystem; - use agentfs_sdk::{AgentFS, AgentFSOptions}; + use crate::server::vfs::NFSFileSystem; + use agentfs_core::fs::AgentFS as CoreAgentFS; + use agentfs_core::{AgentFS, AgentFSOptions, HostFS, OverlayFS}; use std::sync::Arc; + use tempfile::TempDir; async fn test_nfs() -> AgentNFS { let agent = AgentFS::open(AgentFSOptions::ephemeral()) @@ -687,6 +700,27 @@ mod tests { AgentNFS::new(fs) } + async fn overlay_nfs_with_base_file() -> (AgentNFS, TempDir, TempDir) { + let base_dir = tempfile::tempdir().expect("base tempdir is created"); + std::fs::write(base_dir.path().join("base.txt"), b"base").expect("base file is written"); + let base: Arc = + Arc::new(HostFS::new(base_dir.path()).expect("host fs opens base dir")); + + let delta_dir = tempfile::tempdir().expect("delta tempdir is created"); + let db_path = delta_dir.path().join("delta.db"); + let delta = CoreAgentFS::new(db_path.to_str().expect("delta DB path is UTF-8")) + .await + .expect("delta AgentFS opens"); + let overlay = OverlayFS::new(base, delta); + overlay + .init(base_dir.path().to_str().expect("base path is UTF-8")) + .await + .expect("overlay schema initializes"); + let fs: Arc = Arc::new(overlay); + + (AgentNFS::new(fs), base_dir, delta_dir) + } + #[tokio::test] async fn write_handle_grants_exact_authority_but_plain_lookup_handle_does_not() { let nfs = test_nfs().await; @@ -734,4 +768,109 @@ mod tests { assert!(matches!(nfs.fh_to_id(&forged_token_fh), Ok(7))); assert!(!nfs.fh_has_write_authority(&forged_token_fh, 7)); } + + #[tokio::test] + async fn readdirplus_handle_reuses_live_create_write_authority() { + let nfs = test_nfs().await; + let write_fh = nfs.id_to_write_fh(7); + assert!(nfs.fh_has_write_authority(&write_fh, 7)); + + let readdirplus_fh = nfs.id_to_readdirplus_fh(7); + assert!(nfs.fh_has_write_authority(&readdirplus_fh, 7)); + } + + #[tokio::test] + async fn overlay_base_read_write_read_returns_post_write_bytes() { + let (nfs, _base_dir, _delta_dir) = overlay_nfs_with_base_file().await; + + let ino = nfs + .lookup(1, &b"base.txt".to_vec().into()) + .await + .expect("base file lookup succeeds"); + let (before, before_eof) = nfs + .read(ino, 0, 64) + .await + .expect("initial base read succeeds"); + assert_eq!(before, b"base"); + assert!(before_eof); + + let post_write = b"post-write"; + let (write_attrs, durability) = nfs + .write(ino, 0, post_write, AckDurability::Committed) + .await + .expect("write copy-up succeeds"); + assert_eq!(durability, AckDurability::Committed); + assert_eq!(write_attrs.size, post_write.len() as u64); + + let (after, after_eof) = nfs + .read(ino, 0, 64) + .await + .expect("post-write read succeeds"); + assert_eq!( + after, post_write, + "read handle cached before copy-up must not serve stale base bytes" + ); + assert!(after_eof); + + let attrs_after = nfs.getattr(ino).await.expect("post-write getattr succeeds"); + assert_eq!(attrs_after.size, post_write.len() as u64); + assert_eq!(attrs_after.size, after.len() as u64); + } + + #[tokio::test] + async fn remove_and_rename_over_invalidate_write_authority() { + let nfs = test_nfs().await; + let auth = auth_unix { + stamp: 0, + machinename: b"test".to_vec(), + uid: 1000, + gid: 1000, + gids: Vec::new(), + }; + + let (victim, _) = nfs + .create(1, &b"victim.txt".to_vec().into(), sattr3::default(), &auth) + .await + .expect("victim create succeeds"); + let victim_fh = nfs.id_to_write_fh(victim); + assert!(nfs.fh_has_write_authority(&victim_fh, victim)); + + nfs.remove(1, &b"victim.txt".to_vec().into()) + .await + .expect("remove succeeds"); + assert!(!nfs.fh_has_write_authority(&victim_fh, victim)); + + let (replaced, _) = nfs + .create( + 1, + &b"replaced.txt".to_vec().into(), + sattr3::default(), + &auth, + ) + .await + .expect("replaced create succeeds"); + let replaced_fh = nfs.id_to_write_fh(replaced); + let (incoming, _) = nfs + .create( + 1, + &b"incoming.txt".to_vec().into(), + sattr3::default(), + &auth, + ) + .await + .expect("incoming create succeeds"); + let incoming_fh = nfs.id_to_write_fh(incoming); + + nfs.rename( + 1, + &b"incoming.txt".to_vec().into(), + 1, + &b"replaced.txt".to_vec().into(), + ) + .await + .expect("rename-over succeeds"); + + assert!(!nfs.fh_has_write_authority(&replaced_fh, replaced)); + assert!(nfs.fh_has_write_authority(&incoming_fh, incoming)); + } } diff --git a/crates/agentfs-nfs/src/lib.rs b/crates/agentfs-nfs/src/lib.rs new file mode 100644 index 00000000..90ced19d --- /dev/null +++ b/crates/agentfs-nfs/src/lib.rs @@ -0,0 +1,240 @@ +//! Sealed NFS serve surface for AgentFS. +//! +//! The adopted NFS protocol server and AgentFS adapter are private +//! implementation details. Callers receive only the serve entry point, serve +//! options, and server handle needed for lifecycle management (`serve`, +//! `NfsServeOptions`, `ServerHandle`). +//! +//! Owned invariants: +//! +//! - Server-side enforcement: permission checks go through the shared +//! `agentfs_core::semantics` access implementation, and write authority is +//! granted only via handle-table tokens (LRU-bounded, invalidated on +//! unlink/rename) — local processes reaching the TCP port are inside the +//! threat model. +//! - Honest durability: a WRITE is acknowledged `FILE_SYNC` only after the +//! bytes are committed through the core durability contract. +//! - Duplicate request handling: retransmitted non-idempotent RPCs replay +//! their recorded reply instead of re-executing. +//! - Graceful shutdown: the accept loop stops on cancellation, in-flight +//! tasks are joined, and the filesystem is finalized before the handle +//! resolves. + +mod adapter; +mod server; + +use std::net::SocketAddr; +use std::sync::Arc; + +use agentfs_core::FileSystem; +use tokio::task::JoinHandle; +use tokio_util::sync::CancellationToken; + +use adapter::AgentNFS; +use server::tcp::{NFSTcp, NFSTcpListener}; + +/// Options for serving an agent filesystem over NFSv3. +#[derive(Debug, Clone)] +pub struct NfsServeOptions { + /// IP address or hostname to bind. + pub bind: String, + /// TCP port to bind. Use `0` to request an ephemeral port. + pub port: u32, +} + +impl NfsServeOptions { + /// Create NFS serve options for the given bind host and port. + pub fn new(bind: impl Into, port: u32) -> Self { + Self { + bind: bind.into(), + port, + } + } + + fn bind_addr(&self) -> anyhow::Result { + if self.port > u16::MAX as u32 { + anyhow::bail!("NFS port {} is outside the valid TCP port range", self.port); + } + Ok(format!("{}:{}", self.bind, self.port)) + } +} + +impl Default for NfsServeOptions { + fn default() -> Self { + Self::new("127.0.0.1", 0) + } +} + +/// Handle for a running NFS server. +pub struct ServerHandle { + shutdown: CancellationToken, + task: Option>>, + local_addr: SocketAddr, +} + +impl ServerHandle { + /// Listening address chosen by the OS. + pub fn local_addr(&self) -> SocketAddr { + self.local_addr + } + + /// Listening TCP port chosen by the OS. + pub fn local_port(&self) -> u16 { + self.local_addr.port() + } + + /// Request cooperative server shutdown. + pub fn cancel(&self) { + self.shutdown.cancel(); + } + + /// Whether the background server task has finished. + pub fn is_finished(&self) -> bool { + self.task + .as_ref() + .map(JoinHandle::is_finished) + .unwrap_or(true) + } + + /// Abort the background server task if it has not stopped cooperatively. + pub fn abort(&mut self) { + if let Some(task) = &self.task { + task.abort(); + } + } + + /// Wait for the server task to stop and surface shutdown errors. + pub async fn join(&mut self) -> anyhow::Result<()> { + let Some(task) = self.task.as_mut() else { + return Ok(()); + }; + // The task must leave the slot as soon as it completes, even on a + // JoinError: a retained handle would re-poll a completed JoinHandle on + // the next join (panic) and trip the unjoined-task warn in Drop. + let result = task.await; + self.task.take(); + result + .map_err(|error| anyhow::anyhow!("NFS server task failed to join: {error}"))? + .map_err(anyhow::Error::from) + } +} + +impl Drop for ServerHandle { + fn drop(&mut self) { + self.shutdown.cancel(); + if let Some(task) = &self.task { + if !task.is_finished() { + task.abort(); + } else { + tracing::warn!( + "dropping a finished NFS server task without joining; call ServerHandle::join to observe shutdown errors" + ); + } + } + } +} + +/// Serve an agent filesystem over NFSv3 until `shutdown` is cancelled. +pub async fn serve( + fs: Arc, + opts: NfsServeOptions, + shutdown: CancellationToken, +) -> anyhow::Result { + let nfs = AgentNFS::new(fs); + let listener = NFSTcpListener::bind(&opts.bind_addr()?, nfs).await?; + let local_addr = SocketAddr::new(listener.get_listen_ip(), listener.get_listen_port()); + let server_shutdown = shutdown.clone(); + let task = tokio::spawn(async move { + let result = listener.handle_until_cancelled(server_shutdown).await; + if let Err(error) = &result { + tracing::error!(%error, "NFS server task exited with error"); + } + result + }); + + Ok(ServerHandle { + shutdown, + task: Some(task), + local_addr, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io; + use std::time::Duration; + + fn test_handle(task: JoinHandle>) -> ServerHandle { + ServerHandle { + shutdown: CancellationToken::new(), + task: Some(task), + local_addr: "127.0.0.1:0".parse().expect("valid socket addr"), + } + } + + #[tokio::test] + async fn join_surfaces_server_task_error() { + let task = tokio::spawn(async { Err(io::Error::other("synthetic NFS serve failure")) }); + let mut handle = test_handle(task); + + let error = handle.join().await.expect_err("join should surface error"); + + assert!( + error.to_string().contains("synthetic NFS serve failure"), + "unexpected join error: {error}" + ); + } + + #[tokio::test] + async fn join_timeout_leaves_server_task_abortable() { + let task = tokio::spawn(async { std::future::pending::>().await }); + let mut handle = test_handle(task); + + let timed_out = tokio::time::timeout(Duration::from_millis(10), handle.join()).await; + assert!(timed_out.is_err(), "join should remain pending"); + assert!( + !handle.is_finished(), + "timed out join must not detach or finish the task" + ); + + handle.abort(); + let aborted = tokio::time::timeout(Duration::from_secs(1), handle.join()) + .await + .expect("aborted task should join promptly"); + assert!( + aborted.is_err(), + "aborted task should report a JoinError on the join path" + ); + } + + #[tokio::test] + async fn join_after_join_error_empties_task_slot() { + let task = tokio::spawn(async { std::future::pending::>().await }); + let mut handle = test_handle(task); + + handle.abort(); + let first = tokio::time::timeout(Duration::from_secs(1), handle.join()) + .await + .expect("aborted task should join promptly"); + assert!(first.is_err(), "abort should surface a JoinError"); + + assert!( + handle.task.is_none(), + "JoinError must still take the task out of the slot (Drop would \ + otherwise warn about an unjoined finished task)" + ); + assert!( + handle.is_finished(), + "handle with an emptied slot reports finished" + ); + + let second = tokio::time::timeout(Duration::from_secs(1), handle.join()) + .await + .expect("second join must not re-poll a completed JoinHandle"); + assert!( + second.is_ok(), + "second join after a JoinError should be a no-op: {second:?}" + ); + } +} diff --git a/cli/src/nfsserve/context.rs b/crates/agentfs-nfs/src/server/context.rs similarity index 90% rename from cli/src/nfsserve/context.rs rename to crates/agentfs-nfs/src/server/context.rs index b87478e8..69c8ada3 100644 --- a/cli/src/nfsserve/context.rs +++ b/crates/agentfs-nfs/src/server/context.rs @@ -2,7 +2,6 @@ use super::transaction_tracker::TransactionTracker; use super::vfs::NFSFileSystem; use std::fmt; use std::sync::Arc; -use tokio::sync::mpsc; #[derive(Clone)] pub struct RPCContext { @@ -10,7 +9,6 @@ pub struct RPCContext { pub client_addr: String, pub auth: super::rpc::auth_unix, pub vfs: Arc, - pub mount_signal: Option>, pub export_name: Arc, pub transaction_tracker: Arc, } diff --git a/crates/agentfs-nfs/src/server/mod.rs b/crates/agentfs-nfs/src/server/mod.rs new file mode 100644 index 00000000..f69ba536 --- /dev/null +++ b/crates/agentfs-nfs/src/server/mod.rs @@ -0,0 +1,21 @@ +#[macro_use] +pub(crate) mod xdr; + +mod context; +mod permissions; +pub(crate) mod rpc; +mod rpcwire; +mod write_counter; + +mod mount; +mod mount_handlers; + +mod portmap; +mod portmap_handlers; + +pub(crate) mod nfs; +mod nfs_handlers; + +pub(crate) mod tcp; +mod transaction_tracker; +pub(crate) mod vfs; diff --git a/cli/src/nfsserve/mount.rs b/crates/agentfs-nfs/src/server/mount.rs similarity index 81% rename from cli/src/nfsserve/mount.rs rename to crates/agentfs-nfs/src/server/mount.rs index 04714baf..4c3dc69e 100644 --- a/cli/src/nfsserve/mount.rs +++ b/crates/agentfs-nfs/src/server/mount.rs @@ -1,5 +1,4 @@ // this is just a complete enumeration of everything in the RFC -#![allow(dead_code)] // And its nice to keep the original RFC names and case #![allow(non_camel_case_types)] @@ -11,15 +10,9 @@ use std::io::{Read, Write}; // Transcribed from RFC 1057 Appendix A pub const PROGRAM: u32 = 100005; -pub const VERSION: u32 = 3; - -pub const MNTPATHLEN: u32 = 1024; /* Maximum bytes in a path name */ -pub const MNTNAMLEN: u32 = 255; /* Maximum bytes in a name */ -pub const FHSIZE3: u32 = 64; /* Maximum bytes in a V3 file handle */ pub type fhandle3 = Vec; pub type dirpath = Vec; -pub type name = Vec; #[allow(non_camel_case_types)] #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive)] diff --git a/cli/src/nfsserve/mount_handlers.rs b/crates/agentfs-nfs/src/server/mount_handlers.rs similarity index 94% rename from cli/src/nfsserve/mount_handlers.rs rename to crates/agentfs-nfs/src/server/mount_handlers.rs index 063c3a31..1c9e4e53 100644 --- a/cli/src/nfsserve/mount_handlers.rs +++ b/crates/agentfs-nfs/src/server/mount_handlers.rs @@ -115,9 +115,6 @@ pub async fn mountproc3_mnt( ], }; debug!("{:?} --> {:?}", xid, response); - if let Some(ref chan) = context.mount_signal { - let _ = chan.send(true).await; - } make_success_reply(xid).serialize(output)?; mountstat3::MNT3_OK.serialize(output)?; response.serialize(output)?; @@ -185,15 +182,12 @@ pub async fn mountproc3_umnt( xid: u32, input: &mut impl Read, output: &mut impl Write, - context: &RPCContext, + _context: &RPCContext, ) -> Result<(), anyhow::Error> { let mut path = dirpath::new(); path.deserialize(input)?; let utf8path = std::str::from_utf8(&path).unwrap_or_default(); debug!("mountproc3_umnt({:?},{:?}) ", xid, utf8path); - if let Some(ref chan) = context.mount_signal { - let _ = chan.send(false).await; - } make_success_reply(xid).serialize(output)?; mountstat3::MNT3_OK.serialize(output)?; Ok(()) @@ -203,12 +197,9 @@ pub async fn mountproc3_umnt_all( xid: u32, _input: &mut impl Read, output: &mut impl Write, - context: &RPCContext, + _context: &RPCContext, ) -> Result<(), anyhow::Error> { debug!("mountproc3_umnt_all({:?}) ", xid); - if let Some(ref chan) = context.mount_signal { - let _ = chan.send(false).await; - } make_success_reply(xid).serialize(output)?; mountstat3::MNT3_OK.serialize(output)?; Ok(()) diff --git a/cli/src/nfsserve/nfs.rs b/crates/agentfs-nfs/src/server/nfs.rs similarity index 95% rename from cli/src/nfsserve/nfs.rs rename to crates/agentfs-nfs/src/server/nfs.rs index 618f19ba..6b6968a6 100644 --- a/cli/src/nfsserve/nfs.rs +++ b/crates/agentfs-nfs/src/server/nfs.rs @@ -1,11 +1,9 @@ // this is just a complete enumeration of everything in the RFC -#![allow(dead_code)] // And its nice to keep the original RFC names and case #![allow(non_camel_case_types)] use super::xdr::*; use byteorder::{ReadBytesExt, WriteBytesExt}; -use filetime; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::cast::FromPrimitive; use std::fmt; @@ -21,17 +19,10 @@ pub const VERSION: u32 = 3; // Section 2.4 Sizes // -/// The maximum size in bytes of the opaque file handle. -pub const NFS3_FHSIZE: u32 = 64; - /// The size in bytes of the opaque cookie verifier passed by /// READDIR and READDIRPLUS. pub const NFS3_COOKIEVERFSIZE: u32 = 8; -/// The size in bytes of the opaque verifier used for -/// exclusive CREATE. -pub const NFS3_CREATEVERFSIZE: u32 = 8; - /// The size in bytes of the opaque verifier used for /// asynchronous WRITE. pub const NFS3_WRITEVERFSIZE: u32 = 8; @@ -44,9 +35,6 @@ impl nfsstring { pub fn len(&self) -> usize { self.0.len() } - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } } impl From> for nfsstring { fn from(value: Vec) -> Self { @@ -87,7 +75,6 @@ pub type nfspath3 = nfsstring; pub type fileid3 = u64; pub type cookie3 = u64; pub type cookieverf3 = [opaque; NFS3_COOKIEVERFSIZE as usize]; -pub type createverf3 = [opaque; NFS3_CREATEVERFSIZE as usize]; pub type writeverf3 = [opaque; NFS3_WRITEVERFSIZE as usize]; pub type uid3 = u32; pub type gid3 = u32; @@ -143,9 +130,6 @@ pub enum nfsstat3 { /// No space left on device. The operation would have caused /// the server's file system to exceed its limit. NFS3ERR_NOSPC = 28, - /// Read-only file system. A modifying operation was - /// attempted on a read-only file system. - NFS3ERR_ROFS = 30, /// Too many hard links. NFS3ERR_MLINK = 31, /// The filename in an operation was too long. @@ -283,9 +267,6 @@ XDRStruct!( // The following constants are used in fsinfo to construct the bitmask 'properties', // which represents the file system properties. -/// If this bit is 1 (TRUE), the file system supports hard links. -pub const FSF_LINK: u32 = 0x0001; - /// If this bit is 1 (TRUE), the file system supports symbolic links. pub const FSF_SYMLINK: u32 = 0x0002; @@ -559,8 +540,3 @@ pub struct symlinkdata3 { pub symlink_data: nfspath3, } XDRStruct!(symlinkdata3, symlink_attributes, symlink_data); - -/// We define the root handle here -pub fn get_root_mount_handle() -> Vec { - vec![0] -} diff --git a/cli/src/nfsserve/nfs_handlers.rs b/crates/agentfs-nfs/src/server/nfs_handlers.rs similarity index 73% rename from cli/src/nfsserve/nfs_handlers.rs rename to crates/agentfs-nfs/src/server/nfs_handlers.rs index a5ce81d2..9cbc2b4f 100644 --- a/cli/src/nfsserve/nfs_handlers.rs +++ b/crates/agentfs-nfs/src/server/nfs_handlers.rs @@ -1,11 +1,11 @@ #![allow(clippy::upper_case_acronyms)] -#![allow(dead_code)] use super::context::RPCContext; use super::nfs; use super::permissions; use super::rpc::*; -use super::vfs::VFSCapabilities; use super::xdr::*; +use agentfs_core::fs::MAX_NAME_LEN; +use agentfs_core::semantics::{access, AckDurability}; use byteorder::{ReadBytesExt, WriteBytesExt}; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::cast::FromPrimitive; @@ -283,8 +283,10 @@ pub async fn nfsproc3_lookup( } }; + let creds = permissions::credentials(&context.auth); + let dir_stats = permissions::stats(&dir_attr_full); // Check execute (search) permission on directory - if !permissions::can_execute(&context.auth, &dir_attr_full) { + if !access::may_search(&dir_stats, &creds) { debug!( "lookup permission denied for uid={} on directory", context.auth.uid @@ -394,8 +396,10 @@ pub async fn nfsproc3_read( } }; + let creds = permissions::credentials(&context.auth); + let stats = permissions::stats(&attr); // Check read permission - if !permissions::can_read(&context.auth, &attr) { + if !access::may_read(&stats, &creds) { debug!("read permission denied for uid={}", context.auth.uid); make_success_reply(xid).serialize(output)?; nfs::nfsstat3::NFS3ERR_ACCES.serialize(output)?; @@ -564,13 +568,7 @@ pub async fn nfsproc3_access( }; // Compute access based on auth credentials and file attributes - let mut granted_access = permissions::compute_access(&context.auth, &attr, requested_access); - - // If filesystem is read-only, remove write permissions - if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { - granted_access &= - permissions::ACCESS3_READ | permissions::ACCESS3_LOOKUP | permissions::ACCESS3_EXECUTE; - } + let granted_access = permissions::compute_access(&context.auth, &attr, requested_access); debug!( " {:?} ---> requested={:?}, granted={:?}", @@ -660,7 +658,7 @@ pub async fn nfsproc3_pathconf( let res = PATHCONF3resok { obj_attributes: obj_attr, linkmax: 0, - name_max: 32768, + name_max: MAX_NAME_LEN as u32, no_trunc: true, chown_restricted: true, case_insensitive: false, @@ -880,8 +878,10 @@ pub async fn nfsproc3_readdirplus( } }; + let creds = permissions::credentials(&context.auth); + let dir_stats = permissions::stats(&dir_attr_full); // Check read permission on directory - if !permissions::can_read(&context.auth, &dir_attr_full) { + if !access::may_read(&dir_stats, &creds) { debug!( "readdirplus permission denied for uid={} on directory", context.auth.uid @@ -968,7 +968,7 @@ pub async fn nfsproc3_readdirplus( return Ok(()); }*/ // subtract off the final entryplus* field (which must be false) and the eof - let max_bytes_allowed = args.maxcount as usize - 128; + let max_bytes_allowed = (args.maxcount as usize).saturating_sub(128); // args.dircount is bytes of just fileid, name, cookie. // This is hard to ballpark, so we just divide it by 16 let estimated_max_results = args.dircount / 16; @@ -976,7 +976,7 @@ pub async fn nfsproc3_readdirplus( let mut ctr = 0; match context .vfs - .readdir(dirid, args.cookie, estimated_max_results as usize) + .readdir(dirid, args.cookie, (estimated_max_results as usize).max(1)) .await { Ok(result) => { @@ -994,12 +994,13 @@ pub async fn nfsproc3_readdirplus( dirversion.serialize(&mut counting_output)?; for entry in result.entries { let obj_attr = entry.attr; - let handle = nfs::post_op_fh3::handle(context.vfs.id_to_fh(entry.fileid)); + let handle = + nfs::post_op_fh3::handle(context.vfs.id_to_readdirplus_fh(entry.fileid)); let entry = entryplus3 { fileid: entry.fileid, name: entry.name, - cookie: entry.fileid, + cookie: entry.cookie, name_attributes: nfs::post_op_attr::attributes(obj_attr), name_handle: handle, }; @@ -1092,8 +1093,10 @@ pub async fn nfsproc3_readdir( } }; + let creds = permissions::credentials(&context.auth); + let dir_stats = permissions::stats(&dir_attr_full); // Check read permission on directory - if !permissions::can_read(&context.auth, &dir_attr_full) { + if !access::may_read(&dir_stats, &creds) { debug!( "readdir permission denied for uid={} on directory", context.auth.uid @@ -1117,14 +1120,14 @@ pub async fn nfsproc3_readdir( debug!(" -- Dir version {:?}", dirversion); let has_version = args.cookieverf != nfs::cookieverf3::default(); // subtract off the final entryplus* field (which must be false) and the eof - let max_bytes_allowed = args.dircount as usize - 128; + let max_bytes_allowed = (args.dircount as usize).saturating_sub(128); // args.dircount is bytes of just fileid, name, cookie. // This is hard to ballpark, so we just divide it by 16 let estimated_max_results = args.dircount / 16; let mut ctr = 0; match context .vfs - .readdir_simple(dirid, estimated_max_results as usize) + .readdir_simple(dirid, args.cookie, (estimated_max_results as usize).max(1)) .await { Ok(result) => { @@ -1144,7 +1147,7 @@ pub async fn nfsproc3_readdir( let entry = entry3 { fileid: entry.fileid, name: entry.name, - cookie: entry.fileid, + cookie: entry.cookie, }; // write the entry into a buffer first let mut write_buf: Vec = Vec::new(); @@ -1202,7 +1205,7 @@ pub async fn nfsproc3_readdir( } #[allow(non_camel_case_types)] -#[derive(Copy, Clone, Debug, Default, FromPrimitive, ToPrimitive)] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, FromPrimitive, ToPrimitive)] #[repr(u32)] pub enum stable_how { #[default] @@ -1212,6 +1215,21 @@ pub enum stable_how { } XDREnumSerde!(stable_how); +fn ack_durability_for_write_request(_stable: u32) -> AckDurability { + // D3's current NFS policy is FILE_SYNC-honest for every WRITE: even if a + // client asks for UNSTABLE, this server commits before acknowledging and + // reports FILE_SYNC. UNSTABLE+COMMIT can later opt in by changing this one + // mapping. + AckDurability::Committed +} + +fn stable_how_for_ack_durability(durability: AckDurability) -> stable_how { + match durability { + AckDurability::Volatile => stable_how::UNSTABLE, + AckDurability::Committed => stable_how::FILE_SYNC, + } +} + #[allow(non_camel_case_types)] #[derive(Debug, Default)] struct WRITE3args { @@ -1275,15 +1293,6 @@ pub async fn nfsproc3_write( output: &mut impl Write, context: &RPCContext, ) -> Result<(), anyhow::Error> { - // if we do not have write capabilities - if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { - warn!("No write capabilities."); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; - nfs::wcc_data::default().serialize(output)?; - return Ok(()); - } - let mut args = WRITE3args::default(); args.deserialize(input)?; debug!("nfsproc3_write({:?},...) ", xid); @@ -1313,14 +1322,15 @@ pub async fn nfsproc3_write( } }; + let creds = permissions::credentials(&context.auth); + let stats = permissions::stats(&attr); + // Check write permission. NFSv3 is stateless and has no OPEN RPC, but the // file handle returned by CREATE represents the client's open write path. // Honor write authority captured in that handle so git loose objects can // be created with a read-only final mode and still receive writes through // the same handle; fresh LOOKUP handles still fall back to mode checks. - if !context.vfs.fh_has_write_authority(&args.file, id) - && !permissions::can_write(&context.auth, &attr) - { + if !context.vfs.fh_has_write_authority(&args.file, id) && !access::may_write(&stats, &creds) { debug!("write permission denied for uid={}", context.auth.uid); let pre_obj_attr = nfs::pre_op_attr::attributes(nfs::wcc_attr { size: attr.size, @@ -1343,11 +1353,17 @@ pub async fn nfsproc3_write( ctime: attr.ctime, }); - match context.vfs.write(id, args.offset, &args.data).await { - Ok(mut fattr) => { - // POSIX: Clear SUID/SGID bits when a non-root user writes to a file - if context.auth.uid != 0 && fattr.mode & 0o6000 != 0 { - let cleared_mode = fattr.mode & !0o6000; + let requested_durability = ack_durability_for_write_request(args.stable); + match context + .vfs + .write(id, args.offset, &args.data, requested_durability) + .await + { + Ok((mut fattr, ack_durability)) => { + // POSIX: Clear SUID/SGID bits when file contents change. + let written_stats = permissions::stats(&fattr); + let cleared_mode = permissions::without_killpriv(&written_stats, &creds, fattr.mode); + if cleared_mode != fattr.mode { let clear_sattr = nfs::sattr3 { mode: nfs::set_mode3::mode(cleared_mode), ..Default::default() @@ -1363,7 +1379,7 @@ pub async fn nfsproc3_write( after: nfs::post_op_attr::attributes(fattr), }, count: args.count, - committed: stable_how::FILE_SYNC, + committed: stable_how_for_ack_durability(ack_durability), verf: context.vfs.serverid(), }; make_success_reply(xid).serialize(output)?; @@ -1436,15 +1452,6 @@ pub async fn nfsproc3_create( output: &mut impl Write, context: &RPCContext, ) -> Result<(), anyhow::Error> { - // if we do not have write capabilities - if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { - warn!("No write capabilities."); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; - nfs::wcc_data::default().serialize(output)?; - return Ok(()); - } - let mut dirops = nfs::diropargs3::default(); dirops.deserialize(input)?; let mut createhow = createmode3::default(); @@ -1484,8 +1491,10 @@ pub async fn nfsproc3_create( ctime: dir_attr.ctime, }); + let creds = permissions::credentials(&context.auth); + let dir_stats = permissions::stats(&dir_attr); // Check write and execute permission on parent directory - if !permissions::can_modify_directory(&context.auth, &dir_attr) { + if !access::may_modify_directory(&dir_stats, &creds) { debug!( "create permission denied for uid={} on directory", context.auth.uid @@ -1652,13 +1661,6 @@ pub async fn nfsproc3_setattr( output: &mut impl Write, context: &RPCContext, ) -> Result<(), anyhow::Error> { - if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { - warn!("No write capabilities."); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; - nfs::wcc_data::default().serialize(output)?; - return Ok(()); - } let mut args = SETATTR3args::default(); args.deserialize(input)?; debug!("nfsproc3_setattr({:?},{:?}) ", xid, args); @@ -1689,45 +1691,21 @@ pub async fn nfsproc3_setattr( ctime: attr.ctime, }); - // Check permissions based on what's being changed - // For size change (truncate), need write permission - if matches!(args.new_attribute.size, nfs::set_size3::size(_)) - && !context.vfs.fh_has_write_authority(&args.object, id) - && !permissions::can_write(&context.auth, &attr) - { - debug!( - "setattr (truncate) permission denied for uid={}", - context.auth.uid - ); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_ACCES.serialize(output)?; - nfs::wcc_data { - before: pre_op_attr, - after: nfs::post_op_attr::attributes(attr), - } - .serialize(output)?; - return Ok(()); + let creds = permissions::credentials(&context.auth); + let stats = permissions::stats(&attr); + let original_change = permissions::attr_change(&args.new_attribute); + let mut authorized_change = original_change; + if original_change.size && context.vfs.fh_has_write_authority(&args.object, id) { + authorized_change.size = false; } - // For mode/uid/gid changes, check permissions per POSIX semantics - let changing_mode = matches!(args.new_attribute.mode, nfs::set_mode3::mode(_)); - let new_uid = match args.new_attribute.uid { - nfs::set_uid3::uid(u) => Some(u), - nfs::set_uid3::Void => None, - }; - let new_gid = match args.new_attribute.gid { - nfs::set_gid3::gid(g) => Some(g), - nfs::set_gid3::Void => None, - }; - - // For chmod: must be owner (or root) - if changing_mode && !permissions::is_owner(&context.auth, &attr) { + if let Err(error) = access::setattr_allowed(&stats, &creds, &authorized_change) { debug!( - "setattr (chmod) permission denied for uid={}, file owner={}", - context.auth.uid, attr.uid + "setattr permission denied for uid={} on ino={}: {}", + context.auth.uid, id, error ); make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_PERM.serialize(output)?; + permissions::denial_status(&error).serialize(output)?; nfs::wcc_data { before: pre_op_attr, after: nfs::post_op_attr::attributes(attr), @@ -1736,113 +1714,17 @@ pub async fn nfsproc3_setattr( return Ok(()); } - // For chown (changing uid): only root can change to a different user. - // An owner can "chown" to themselves (no-op on uid). - if let Some(target_uid) = new_uid { - if context.auth.uid != 0 && target_uid != attr.uid { - debug!( - "setattr (chown uid) permission denied: only root can change file owner, caller uid={}", - context.auth.uid - ); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_PERM.serialize(output)?; - nfs::wcc_data { - before: pre_op_attr, - after: nfs::post_op_attr::attributes(attr), - } - .serialize(output)?; - return Ok(()); - } - } - - // For chgrp (changing gid): root can change to any group, - // owner can change to a group they belong to - if let Some(target_gid) = new_gid { - if context.auth.uid != 0 { - // Must be owner - if !permissions::is_owner(&context.auth, &attr) { - debug!( - "setattr (chgrp) permission denied: not owner, uid={}, file owner={}", - context.auth.uid, attr.uid - ); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_PERM.serialize(output)?; - nfs::wcc_data { - before: pre_op_attr, - after: nfs::post_op_attr::attributes(attr), - } - .serialize(output)?; - return Ok(()); - } - // Owner can only change to a group they belong to - let in_target_group = - context.auth.gid == target_gid || context.auth.gids.contains(&target_gid); - if !in_target_group { - debug!( - "setattr (chgrp) permission denied: uid={} not member of target group {}", - context.auth.uid, target_gid - ); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_PERM.serialize(output)?; - nfs::wcc_data { - before: pre_op_attr, - after: nfs::post_op_attr::attributes(attr), - } - .serialize(output)?; - return Ok(()); - } - } + if let Some(mode) = permissions::normalize_setattr_mode(&stats, &creds, &original_change) { + args.new_attribute.mode = nfs::set_mode3::mode(mode); } - // For setting explicit timestamps (SET_TO_CLIENT_TIME): must be owner or root. - // SET_TO_SERVER_TIME only requires write access (not checked here). - let setting_explicit_atime = matches!( - args.new_attribute.atime, - nfs::set_atime::SET_TO_CLIENT_TIME(_) - ); - let setting_explicit_mtime = matches!( - args.new_attribute.mtime, - nfs::set_mtime::SET_TO_CLIENT_TIME(_) - ); - if (setting_explicit_atime || setting_explicit_mtime) - && !permissions::is_owner(&context.auth, &attr) + // POSIX: writes/truncates and owner/group changes clear SUID/SGID + // unless this SETATTR also supplies an explicit mode. + if (original_change.size || original_change.uid.is_some() || original_change.gid.is_some()) + && original_change.mode.is_none() { - debug!( - "setattr (utimes) permission denied: uid={} is not owner={}", - context.auth.uid, attr.uid - ); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_PERM.serialize(output)?; - nfs::wcc_data { - before: pre_op_attr, - after: nfs::post_op_attr::attributes(attr), - } - .serialize(output)?; - return Ok(()); - } - - // POSIX: If a non-root user sets S_ISGID via chmod on a regular file and - // the user is not a member of the file's group, silently clear S_ISGID. - if changing_mode && context.auth.uid != 0 { - if let nfs::set_mode3::mode(ref mut mode) = args.new_attribute.mode { - if *mode & 0o2000 != 0 { - let file_gid = new_gid.unwrap_or(attr.gid); - let in_group = - context.auth.gid == file_gid || context.auth.gids.contains(&file_gid); - if !in_group { - *mode &= !0o2000; - } - } - } - } - - // POSIX: When a non-root user changes uid or gid, clear SUID/SGID bits. - // If the caller is not root and is performing a chown/chgrp, and is not - // explicitly setting mode, we must strip the SUID and SGID bits. - if context.auth.uid != 0 && (new_uid.is_some() || new_gid.is_some()) && !changing_mode { - let current_mode = attr.mode; - if current_mode & 0o6000 != 0 { - let cleared_mode = current_mode & !0o6000; + let cleared_mode = permissions::without_killpriv(&stats, &creds, attr.mode); + if cleared_mode != attr.mode { args.new_attribute.mode = nfs::set_mode3::mode(cleared_mode); } } @@ -1912,15 +1794,6 @@ pub async fn nfsproc3_remove( output: &mut impl Write, context: &RPCContext, ) -> Result<(), anyhow::Error> { - // if we do not have write capabilities - if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { - warn!("No write capabilities."); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; - nfs::wcc_data::default().serialize(output)?; - return Ok(()); - } - let mut dirops = nfs::diropargs3::default(); dirops.deserialize(input)?; @@ -1956,20 +1829,18 @@ pub async fn nfsproc3_remove( ctime: dir_attr.ctime, }); + let creds = permissions::credentials(&context.auth); + let dir_stats = permissions::stats(&dir_attr); // Check permission to delete entry (includes sticky bit check) - let remove_allowed = if (dir_attr.mode & permissions::S_ISVTX) != 0 { - // Sticky bit set: need to look up entry attributes - match context.vfs.lookup(dirid, &dirops.name).await { - Ok(entry_id) => match context.vfs.getattr(entry_id).await { - Ok(entry_attr) => { - permissions::can_delete_entry(&context.auth, &dir_attr, &entry_attr) - } - Err(_) => permissions::can_modify_directory(&context.auth, &dir_attr), - }, - Err(_) => permissions::can_modify_directory(&context.auth, &dir_attr), - } - } else { - permissions::can_modify_directory(&context.auth, &dir_attr) + let remove_allowed = match context.vfs.lookup(dirid, &dirops.name).await { + Ok(entry_id) => match context.vfs.getattr(entry_id).await { + Ok(entry_attr) => { + let entry_stats = permissions::stats(&entry_attr); + access::sticky_delete_ok(&dir_stats, &entry_stats, &creds) + } + Err(_) => access::may_modify_directory(&dir_stats, &creds), + }, + Err(_) => access::may_modify_directory(&dir_stats, &creds), }; if !remove_allowed { @@ -2055,15 +1926,6 @@ pub async fn nfsproc3_rename( output: &mut impl Write, context: &RPCContext, ) -> Result<(), anyhow::Error> { - // if we do not have write capabilities - if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { - warn!("No write capabilities."); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; - nfs::wcc_data::default().serialize(output)?; - return Ok(()); - } - let mut fromdirops = nfs::diropargs3::default(); let mut todirops = nfs::diropargs3::default(); fromdirops.deserialize(input)?; @@ -2136,33 +1998,24 @@ pub async fn nfsproc3_rename( ctime: to_dir_attr.ctime, }); + let creds = permissions::credentials(&context.auth); + let from_dir_stats = permissions::stats(&from_dir_attr); + let to_dir_stats = permissions::stats(&to_dir_attr); // Check permission on source directory (includes sticky bit check) - let from_allowed = if (from_dir_attr.mode & permissions::S_ISVTX) != 0 { - match context.vfs.lookup(from_dirid, &fromdirops.name).await { - Ok(entry_id) => match context.vfs.getattr(entry_id).await { - Ok(entry_attr) => { - let basic = - permissions::can_delete_entry(&context.auth, &from_dir_attr, &entry_attr); - // For directory entries being renamed across parents, the caller - // must own the entry (not just the sticky directory), because the - // operation modifies the directory's ".." link. - if basic - && matches!(entry_attr.ftype, nfs::ftype3::NF3DIR) - && from_dirid != to_dirid - && context.auth.uid != 0 - && context.auth.uid != entry_attr.uid - { - false - } else { - basic - } - } - Err(_) => permissions::can_modify_directory(&context.auth, &from_dir_attr), - }, - Err(_) => permissions::can_modify_directory(&context.auth, &from_dir_attr), - } - } else { - permissions::can_modify_directory(&context.auth, &from_dir_attr) + let from_allowed = match context.vfs.lookup(from_dirid, &fromdirops.name).await { + Ok(entry_id) => match context.vfs.getattr(entry_id).await { + Ok(entry_attr) => { + let entry_stats = permissions::stats(&entry_attr); + access::sticky_rename_from_ok( + &from_dir_stats, + &entry_stats, + &creds, + from_dirid != to_dirid, + ) + } + Err(_) => access::may_modify_directory(&from_dir_stats, &creds), + }, + Err(_) => access::may_modify_directory(&from_dir_stats, &creds), }; if !from_allowed { @@ -2194,20 +2047,16 @@ pub async fn nfsproc3_rename( } // Check permission on target directory (includes sticky bit check if dest exists) - let to_allowed = if (to_dir_attr.mode & permissions::S_ISVTX) != 0 { - // Sticky bit on target dir: check if destination entry exists - match context.vfs.lookup(to_dirid, &todirops.name).await { - Ok(entry_id) => match context.vfs.getattr(entry_id).await { - Ok(entry_attr) => { - permissions::can_delete_entry(&context.auth, &to_dir_attr, &entry_attr) - } - Err(_) => permissions::can_modify_directory(&context.auth, &to_dir_attr), - }, - // Dest doesn't exist, just need write+execute on target dir - Err(_) => permissions::can_modify_directory(&context.auth, &to_dir_attr), - } - } else { - permissions::can_modify_directory(&context.auth, &to_dir_attr) + let to_allowed = match context.vfs.lookup(to_dirid, &todirops.name).await { + Ok(entry_id) => match context.vfs.getattr(entry_id).await { + Ok(entry_attr) => { + let entry_stats = permissions::stats(&entry_attr); + access::sticky_delete_ok(&to_dir_stats, &entry_stats, &creds) + } + Err(_) => access::may_modify_directory(&to_dir_stats, &creds), + }, + // Dest doesn't exist, just need write+execute on target dir + Err(_) => access::may_modify_directory(&to_dir_stats, &creds), }; if !to_allowed { @@ -2325,14 +2174,6 @@ pub async fn nfsproc3_mkdir( output: &mut impl Write, context: &RPCContext, ) -> Result<(), anyhow::Error> { - // if we do not have write capabilities - if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { - warn!("No write capabilities."); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; - nfs::wcc_data::default().serialize(output)?; - return Ok(()); - } let mut args = MKDIR3args::default(); args.deserialize(input)?; @@ -2370,8 +2211,10 @@ pub async fn nfsproc3_mkdir( ctime: dir_attr.ctime, }); + let creds = permissions::credentials(&context.auth); + let dir_stats = permissions::stats(&dir_attr); // Check write and execute permission on parent directory - if !permissions::can_modify_directory(&context.auth, &dir_attr) { + if !access::may_modify_directory(&dir_stats, &creds) { debug!( "mkdir permission denied for uid={} on directory", context.auth.uid @@ -2473,14 +2316,6 @@ pub async fn nfsproc3_symlink( output: &mut impl Write, context: &RPCContext, ) -> Result<(), anyhow::Error> { - // if we do not have write capabilities - if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { - warn!("No write capabilities."); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; - nfs::wcc_data::default().serialize(output)?; - return Ok(()); - } let mut args = SYMLINK3args::default(); args.deserialize(input)?; @@ -2518,8 +2353,10 @@ pub async fn nfsproc3_symlink( ctime: dir_attr.ctime, }); + let creds = permissions::credentials(&context.auth); + let dir_stats = permissions::stats(&dir_attr); // Check write and execute permission on parent directory - if !permissions::can_modify_directory(&context.auth, &dir_attr) { + if !access::may_modify_directory(&dir_stats, &creds) { debug!( "symlink permission denied for uid={} on directory", context.auth.uid @@ -2622,16 +2459,6 @@ pub async fn nfsproc3_link( output: &mut impl Write, context: &RPCContext, ) -> Result<(), anyhow::Error> { - // if we do not have write capabilities - if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { - warn!("No write capabilities."); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; - nfs::post_op_attr::Void.serialize(output)?; - nfs::wcc_data::default().serialize(output)?; - return Ok(()); - } - let mut args = LINK3args::default(); args.deserialize(input)?; @@ -2698,8 +2525,10 @@ pub async fn nfsproc3_link( ctime: dir_attr.ctime, }); + let creds = permissions::credentials(&context.auth); + let dir_stats = permissions::stats(&dir_attr); // Check write and execute permission on directory - if !permissions::can_modify_directory(&context.auth, &dir_attr) { + if !access::may_modify_directory(&dir_stats, &creds) { debug!( "link permission denied for uid={} on directory", context.auth.uid @@ -2875,15 +2704,6 @@ pub async fn nfsproc3_mknod( output: &mut impl Write, context: &RPCContext, ) -> Result<(), anyhow::Error> { - // if we do not have write capabilities - if !matches!(context.vfs.capabilities(), VFSCapabilities::ReadWrite) { - warn!("No write capabilities."); - make_success_reply(xid).serialize(output)?; - nfs::nfsstat3::NFS3ERR_ROFS.serialize(output)?; - nfs::wcc_data::default().serialize(output)?; - return Ok(()); - } - // Read diropargs3 (where to create) let mut dirops = nfs::diropargs3::default(); dirops.deserialize(input)?; @@ -2949,8 +2769,10 @@ pub async fn nfsproc3_mknod( ctime: dir_attr.ctime, }); + let creds = permissions::credentials(&context.auth); + let dir_stats = permissions::stats(&dir_attr); // Check write and execute permission on parent directory - if !permissions::can_modify_directory(&context.auth, &dir_attr) { + if !access::may_modify_directory(&dir_stats, &creds) { debug!( "mknod permission denied for uid={} on directory", context.auth.uid @@ -3008,22 +2830,64 @@ pub async fn nfsproc3_mknod( #[cfg(test)] mod tests { use super::*; - use crate::nfs::AgentNFS; - use crate::nfsserve::rpc::{accept_body, accepted_reply, reply_body, rpc_body, rpc_msg}; - use crate::nfsserve::transaction_tracker::TransactionTracker; - use crate::nfsserve::vfs::NFSFileSystem; - use agentfs_sdk::{AgentFS as AgentSdk, AgentFSOptions, FileSystem}; + use crate::adapter::AgentNFS; + use crate::server::rpc::{accept_body, accepted_reply, reply_body, rpc_body, rpc_msg}; + use crate::server::transaction_tracker::{TransactionTracker, DEFAULT_REPLY_CACHE_CAPACITY}; + use crate::server::vfs::NFSFileSystem; + use agentfs_core::fs::MAX_NAME_LEN; + use agentfs_core::{AgentFS as AgentSdk, AgentFSOptions, FileSystem}; use std::io::Cursor; + use std::path::Path; use std::sync::Arc; - use std::time::Duration; const TEST_UID: u32 = 1000; const TEST_GID: u32 = 1000; - async fn test_context() -> (RPCContext, agentfs_sdk::filesystem::AgentFS) { + fn make_auth(uid: u32, gid: u32, gids: Vec) -> auth_unix { + auth_unix { + stamp: 0, + machinename: Vec::new(), + uid, + gid, + gids, + } + } + + fn make_attr(mode: u32, uid: u32, gid: u32, ftype: nfs::ftype3) -> nfs::fattr3 { + nfs::fattr3 { + ftype, + mode, + nlink: 1, + uid, + gid, + size: 0, + used: 0, + rdev: nfs::specdata3::default(), + fsid: 0, + fileid: 2, + atime: nfs::nfstime3::default(), + mtime: nfs::nfstime3::default(), + ctime: nfs::nfstime3::default(), + } + } + + async fn test_context() -> (RPCContext, agentfs_core::fs::AgentFS) { let agent = AgentSdk::open(AgentFSOptions::ephemeral()) .await .expect("open ephemeral AgentFS"); + test_context_from_agent(agent).await + } + + async fn test_context_with_db_path(db_path: &Path) -> (RPCContext, agentfs_core::fs::AgentFS) { + let agent = AgentSdk::open(AgentFSOptions::with_path( + db_path.to_str().expect("test DB path is UTF-8"), + )) + .await + .expect("open file-backed AgentFS"); + test_context_from_agent(agent).await + } + + async fn test_context_from_agent(agent: AgentSdk) -> (RPCContext, agentfs_core::fs::AgentFS) { agent .fs .chmod(1, 0o777) @@ -3043,13 +2907,20 @@ mod tests { gids: vec![TEST_GID], }, vfs, - mount_signal: None, export_name: Arc::new("/".to_string()), - transaction_tracker: Arc::new(TransactionTracker::new(Duration::from_secs(60))), + transaction_tracker: Arc::new(TransactionTracker::new(DEFAULT_REPLY_CACHE_CAPACITY)), }; (context, fs) } + fn force_long_write_batcher_window() { + std::env::set_var("AGENTFS_FUSE_WRITEBACK", "1"); + std::env::set_var("AGENTFS_OVERLAY_READS", "1"); + std::env::set_var("AGENTFS_BATCH_MS", "60000"); + std::env::set_var("AGENTFS_BATCH_BYTES", "1048576"); + std::env::set_var("AGENTFS_BATCH_GLOBAL_BYTES", "10485760"); + } + fn parse_rpc_success(cursor: &mut Cursor>) { let mut reply = rpc_msg::default(); reply.deserialize(cursor).expect("deserialize RPC reply"); @@ -3106,6 +2977,55 @@ mod tests { input } + fn serialize_readdirplus_args(root_fh: nfs::nfs_fh3) -> Vec { + serialize_readdirplus_page_args(root_fh, 0, 8192, 8192) + } + + fn serialize_readdir_args( + root_fh: nfs::nfs_fh3, + cookie: nfs::cookie3, + dircount: u32, + ) -> Vec { + let mut input = Vec::new(); + let mut cursor = Cursor::new(&mut input); + READDIR3args { + dir: root_fh, + cookie, + cookieverf: nfs::cookieverf3::default(), + dircount, + } + .serialize(&mut cursor) + .expect("serialize READDIR args"); + input + } + + fn serialize_readdirplus_page_args( + root_fh: nfs::nfs_fh3, + cookie: nfs::cookie3, + dircount: u32, + maxcount: u32, + ) -> Vec { + let mut input = Vec::new(); + let mut cursor = Cursor::new(&mut input); + READDIRPLUS3args { + dir: root_fh, + cookie, + cookieverf: nfs::cookieverf3::default(), + dircount, + maxcount, + } + .serialize(&mut cursor) + .expect("serialize READDIRPLUS args"); + input + } + + fn serialize_getattr_args(file: nfs::nfs_fh3) -> Vec { + let mut input = Vec::new(); + let mut cursor = Cursor::new(&mut input); + file.serialize(&mut cursor).expect("serialize GETATTR fh"); + input + } + fn serialize_setattr_size_args(file: nfs::nfs_fh3, size: u64) -> Vec { serialize_setattr_size_args_with_guard(file, size, sattrguard3::Void) } @@ -3130,6 +3050,398 @@ mod tests { input } + #[derive(Clone, Copy)] + enum ReaddirProcedure { + Readdir, + ReaddirPlus, + } + + struct ReaddirPage { + names: Vec>, + cookies: Vec, + eof: bool, + } + + async fn create_numbered_files(context: &RPCContext, count: usize) -> Vec> { + let mut names = Vec::with_capacity(count); + for index in 0..count { + let name = format!("entry-{index:04}").into_bytes(); + context + .vfs + .create( + 1, + &name.clone().into(), + nfs::sattr3::default(), + &context.auth, + ) + .await + .expect("numbered file create succeeds"); + names.push(name); + } + names + } + + async fn read_readdir_page( + context: &RPCContext, + procedure: ReaddirProcedure, + cookie: nfs::cookie3, + ) -> ReaddirPage { + let root = context.vfs.id_to_fh(1); + let mut input = Cursor::new(match procedure { + ReaddirProcedure::Readdir => serialize_readdir_args(root, cookie, 512), + ReaddirProcedure::ReaddirPlus => { + serialize_readdirplus_page_args(root, cookie, 512, 1200) + } + }); + let mut output = Vec::new(); + match procedure { + ReaddirProcedure::Readdir => nfsproc3_readdir(6, &mut input, &mut output, context) + .await + .expect("READDIR handler"), + ReaddirProcedure::ReaddirPlus => { + nfsproc3_readdirplus(7, &mut input, &mut output, context) + .await + .expect("READDIRPLUS handler") + } + } + + let mut cursor = Cursor::new(output); + parse_rpc_success(&mut cursor); + let status = parse_nfs_status(&mut cursor); + assert!(matches!(status, nfs::nfsstat3::NFS3_OK)); + let mut dir_attr = nfs::post_op_attr::Void; + dir_attr + .deserialize(&mut cursor) + .expect("deserialize dir attrs"); + let mut cookieverf = nfs::cookieverf3::default(); + cookieverf + .deserialize(&mut cursor) + .expect("deserialize cookie verifier"); + + let mut names = Vec::new(); + let mut cookies = Vec::new(); + loop { + let mut has_entry = false; + has_entry + .deserialize(&mut cursor) + .expect("deserialize entry presence"); + if !has_entry { + break; + } + match procedure { + ReaddirProcedure::Readdir => { + let mut entry = entry3::default(); + entry.deserialize(&mut cursor).expect("deserialize entry"); + names.push(entry.name.to_vec()); + cookies.push(entry.cookie); + } + ReaddirProcedure::ReaddirPlus => { + let mut entry = entryplus3::default(); + entry + .deserialize(&mut cursor) + .expect("deserialize entryplus"); + names.push(entry.name.to_vec()); + cookies.push(entry.cookie); + } + } + } + let mut eof = false; + eof.deserialize(&mut cursor).expect("deserialize eof"); + assert_eq!( + names.len(), + cookies.len(), + "every returned entry carries a pagination cookie" + ); + assert!( + !names.is_empty() || eof, + "a non-final page must make progress with at least one entry" + ); + ReaddirPage { + names, + cookies, + eof, + } + } + + async fn collect_readdir_pages( + context: &RPCContext, + procedure: ReaddirProcedure, + ) -> Vec { + let mut pages = Vec::new(); + let mut cookie = 0; + for _ in 0..64 { + let page = read_readdir_page(context, procedure, cookie).await; + if let Some(next_cookie) = page.cookies.last().copied() { + assert!( + page.eof || next_cookie != cookie, + "pagination did not honor cookie {cookie}: page repeated the same final cookie" + ); + cookie = next_cookie; + } + let eof = page.eof; + pages.push(page); + if eof { + return pages; + } + } + panic!("READDIR pagination did not reach EOF within the safety bound"); + } + + #[test] + fn nfs_access_cases_match_shared_access() { + let requested = permissions::ACCESS3_READ + | permissions::ACCESS3_LOOKUP + | permissions::ACCESS3_MODIFY + | permissions::ACCESS3_EXTEND + | permissions::ACCESS3_DELETE + | permissions::ACCESS3_EXECUTE; + let cases = [ + ( + "nfs owner regular rwx", + make_auth(1000, 1000, vec![]), + make_attr(0o700, 1000, 2000, nfs::ftype3::NF3REG), + ), + ( + "nfs auxiliary group read", + make_auth(1000, 1000, vec![2000]), + make_attr(0o040, 3000, 2000, nfs::ftype3::NF3REG), + ), + ( + "nfs owner directory rwx", + make_auth(1000, 1000, vec![]), + make_attr(0o700, 1000, 2000, nfs::ftype3::NF3DIR), + ), + ( + "nfs root regular mode zero", + make_auth(0, 0, vec![]), + make_attr(0o000, 1000, 1000, nfs::ftype3::NF3REG), + ), + ]; + + println!("adapter=nfs authority=semantics::access"); + let mut mismatches = 0usize; + for (name, auth, attr) in cases { + let stats = permissions::stats(&attr); + let creds = permissions::credentials(&auth); + let mut shared = 0; + if access::may_read(&stats, &creds) { + shared |= permissions::ACCESS3_READ; + } + if stats.is_directory() && access::may_search(&stats, &creds) { + shared |= permissions::ACCESS3_LOOKUP; + } + if access::may_write(&stats, &creds) { + shared |= permissions::ACCESS3_MODIFY | permissions::ACCESS3_EXTEND; + if stats.is_directory() { + shared |= permissions::ACCESS3_DELETE; + } + } + if !stats.is_directory() && access::may_search(&stats, &creds) { + shared |= permissions::ACCESS3_EXECUTE; + } + + let nfs = permissions::compute_access(&auth, &attr, requested); + println!("{name}: nfs={nfs:#04x} access={shared:#04x}"); + if nfs != shared { + mismatches += 1; + } + } + println!("adapter=nfs access_conformance mismatches={mismatches}"); + assert_eq!(mismatches, 0); + } + + #[tokio::test] + async fn readdir_and_readdirplus_honor_cookies_without_duplicates_or_skips() { + let (context, _fs) = test_context().await; + let expected = create_numbered_files(&context, 24).await; + + for procedure in [ReaddirProcedure::Readdir, ReaddirProcedure::ReaddirPlus] { + let pages = collect_readdir_pages(&context, procedure).await; + assert!( + pages.len() > 1, + "test must force pagination across multiple pages" + ); + + for (index, page) in pages.iter().enumerate() { + assert_eq!( + page.eof, + index + 1 == pages.len(), + "eof must be false until the final page" + ); + } + + let mut flat = Vec::new(); + for page in &pages { + flat.extend(page.names.iter().cloned()); + } + let mut unique = flat.clone(); + unique.sort(); + unique.dedup(); + + println!( + "procedure={} pages={} total={} unique={} first_entries={:?}", + match procedure { + ReaddirProcedure::Readdir => "READDIR", + ReaddirProcedure::ReaddirPlus => "READDIRPLUS", + }, + pages.len(), + flat.len(), + unique.len(), + pages + .iter() + .map(|page| String::from_utf8_lossy(&page.names[0]).into_owned()) + .collect::>() + ); + + assert_eq!( + unique.len(), + flat.len(), + "pagination returned duplicate entries" + ); + assert_eq!(unique, expected, "pagination skipped or reordered entries"); + } + } + + #[tokio::test] + async fn readdir_cookies_are_unique_for_hardlinks() { + let (context, _fs) = test_context().await; + let (fileid, _) = context + .vfs + .create( + 1, + &b"entry-0000".to_vec().into(), + nfs::sattr3::default(), + &context.auth, + ) + .await + .expect("create original hardlink target"); + context + .vfs + .link(fileid, 1, &b"entry-0001".to_vec().into()) + .await + .expect("create second hardlink"); + context + .vfs + .create( + 1, + &b"entry-0002".to_vec().into(), + nfs::sattr3::default(), + &context.auth, + ) + .await + .expect("create trailing entry"); + + let first = context.vfs.readdir(1, 0, 2).await.expect("first page"); + assert_eq!(first.entries.len(), 2); + assert_eq!( + first.entries[0].fileid, first.entries[1].fileid, + "test must page across two hardlinks to one inode" + ); + assert_ne!( + first.entries[0].cookie, first.entries[1].cookie, + "NFS cookies must identify dentries, not just fileids" + ); + + let second = context + .vfs + .readdir(1, first.entries[1].cookie, 2) + .await + .expect("second page"); + assert_eq!( + second + .entries + .iter() + .map(|entry| entry.name.to_vec()) + .collect::>(), + vec![b"entry-0002".to_vec()], + "resuming after the second hardlink must not duplicate or skip entries" + ); + assert!(second.end); + } + + #[tokio::test] + async fn readdir_returns_bad_cookie_when_resume_dentry_is_deleted() { + let (context, _fs) = test_context().await; + create_numbered_files(&context, 2).await; + + let first = context.vfs.readdir(1, 0, 1).await.expect("first page"); + assert_eq!(first.entries.len(), 1); + let stale_cookie = first.entries[0].cookie; + let stale_name = first.entries[0].name.clone(); + context + .vfs + .remove(1, &stale_name) + .await + .expect("remove resume dentry"); + + let err = context + .vfs + .readdir(1, stale_cookie, 1) + .await + .expect_err("stale cookie should be rejected"); + assert!(matches!(err, nfs::nfsstat3::NFS3ERR_BAD_COOKIE)); + } + + #[tokio::test] + async fn pathconf_name_max_and_create_limits_match_sdk_enforcement() { + let (context, _fs) = test_context().await; + let mut input = Cursor::new({ + let mut bytes = Vec::new(); + context + .vfs + .id_to_fh(1) + .serialize(&mut bytes) + .expect("serialize PATHCONF handle"); + bytes + }); + let mut output = Vec::new(); + + nfsproc3_pathconf(8, &mut input, &mut output, &context) + .await + .expect("PATHCONF handler"); + + let mut cursor = Cursor::new(output); + parse_rpc_success(&mut cursor); + let status = parse_nfs_status(&mut cursor); + assert!(matches!(status, nfs::nfsstat3::NFS3_OK)); + let mut pathconf = PATHCONF3resok::default(); + pathconf + .deserialize(&mut cursor) + .expect("deserialize PATHCONF result"); + println!( + "pathconf.name_max={} sdk.MAX_NAME_LEN={}", + pathconf.name_max, MAX_NAME_LEN + ); + assert_eq!(pathconf.name_max, MAX_NAME_LEN as u32); + + let ok_name = vec![b'a'; MAX_NAME_LEN]; + context + .vfs + .create(1, &ok_name.into(), nfs::sattr3::default(), &context.auth) + .await + .expect("255-byte filename create succeeds"); + + let too_long_name = vec![b'b'; MAX_NAME_LEN + 1]; + let err = context + .vfs + .create( + 1, + &too_long_name.into(), + nfs::sattr3::default(), + &context.auth, + ) + .await + .expect_err("256-byte filename create fails"); + assert!(matches!(err, nfs::nfsstat3::NFS3ERR_NAMETOOLONG)); + + let fsinfo = context.vfs.fsinfo(1).await.expect("FSINFO succeeds"); + assert_eq!( + fsinfo.rtpref, + 1024 * 1024, + "rtpref should match the 1MiB macOS mount rsize preference" + ); + } + async fn create_readonly_file(context: &RPCContext) -> nfs::nfs_fh3 { let mut input = Cursor::new(serialize_create_readonly_args(context.vfs.id_to_fh(1))); let mut output = Vec::new(); @@ -3162,6 +3474,95 @@ mod tests { parse_nfs_status(&mut cursor) } + async fn write_file_sync_result( + context: &RPCContext, + file: nfs::nfs_fh3, + data: &[u8], + ) -> (nfs::nfsstat3, Option) { + let mut input = Cursor::new(serialize_write_args(file, data)); + let mut output = Vec::new(); + nfsproc3_write(2, &mut input, &mut output, context) + .await + .expect("WRITE handler"); + + let mut cursor = Cursor::new(output); + parse_rpc_success(&mut cursor); + let status = parse_nfs_status(&mut cursor); + if matches!(status, nfs::nfsstat3::NFS3_OK) { + let mut resok = WRITE3resok::default(); + resok + .deserialize(&mut cursor) + .expect("deserialize WRITE3resok"); + (status, Some(resok)) + } else { + (status, None) + } + } + + async fn readdirplus_handle_for_name(context: &RPCContext, name: &[u8]) -> nfs::nfs_fh3 { + let mut input = Cursor::new(serialize_readdirplus_args(context.vfs.id_to_fh(1))); + let mut output = Vec::new(); + nfsproc3_readdirplus(5, &mut input, &mut output, context) + .await + .expect("READDIRPLUS handler"); + + let mut cursor = Cursor::new(output); + parse_rpc_success(&mut cursor); + let status = parse_nfs_status(&mut cursor); + assert!(matches!(status, nfs::nfsstat3::NFS3_OK)); + let mut dir_attr = nfs::post_op_attr::Void; + dir_attr + .deserialize(&mut cursor) + .expect("deserialize READDIRPLUS dir attrs"); + let mut cookieverf = nfs::cookieverf3::default(); + cookieverf + .deserialize(&mut cursor) + .expect("deserialize READDIRPLUS cookie verifier"); + + loop { + let mut has_entry = false; + has_entry + .deserialize(&mut cursor) + .expect("deserialize READDIRPLUS entry presence"); + if !has_entry { + panic!("READDIRPLUS did not return requested entry {name:?}"); + } + let mut entry = entryplus3::default(); + entry + .deserialize(&mut cursor) + .expect("deserialize READDIRPLUS entry"); + if entry.name.as_slice() == name { + return match entry.name_handle { + nfs::post_op_fh3::handle(fh) => fh, + nfs::post_op_fh3::Void => panic!("READDIRPLUS entry omitted file handle"), + }; + } + } + } + + async fn getattr_result( + context: &RPCContext, + file: nfs::nfs_fh3, + ) -> (nfs::nfsstat3, Option) { + let mut input = Cursor::new(serialize_getattr_args(file)); + let mut output = Vec::new(); + nfsproc3_getattr(4, &mut input, &mut output, context) + .await + .expect("GETATTR handler"); + + let mut cursor = Cursor::new(output); + parse_rpc_success(&mut cursor); + let status = parse_nfs_status(&mut cursor); + if matches!(status, nfs::nfsstat3::NFS3_OK) { + let mut attr = nfs::fattr3::default(); + attr.deserialize(&mut cursor) + .expect("deserialize GETATTR fattr"); + (status, Some(attr)) + } else { + (status, None) + } + } + async fn setattr_size_status( context: &RPCContext, file: nfs::nfs_fh3, @@ -3195,7 +3596,28 @@ mod tests { parse_nfs_status(&mut cursor) } - async fn read_file(fs: &agentfs_sdk::filesystem::AgentFS, name: &str, len: u64) -> Vec { + #[test] + fn write_reply_committed_field_derives_from_ack_durability() { + assert_eq!( + stable_how_for_ack_durability(AckDurability::Committed), + stable_how::FILE_SYNC + ); + assert_eq!( + stable_how_for_ack_durability(AckDurability::Volatile), + stable_how::UNSTABLE + ); + assert_eq!( + ack_durability_for_write_request(stable_how::FILE_SYNC as u32), + AckDurability::Committed + ); + assert_eq!( + ack_durability_for_write_request(stable_how::UNSTABLE as u32), + AckDurability::Committed, + "current NFS policy commits every WRITE until COMMIT support opts in" + ); + } + + async fn read_file(fs: &agentfs_core::fs::AgentFS, name: &str, len: u64) -> Vec { let stats = fs .lookup(1, name) .await @@ -3207,6 +3629,99 @@ mod tests { file.pread(0, len).await.expect("read file") } + #[tokio::test] + async fn file_sync_write_reply_survives_abort_before_batch_timer() { + force_long_write_batcher_window(); + let dir = tempfile::tempdir().expect("tempdir"); + let db_path = dir.path().join("nfs-file-sync.db"); + let (context, fs) = test_context_with_db_path(&db_path).await; + let created_fh = create_readonly_file(&context).await; + let payload = b"FILE_SYNC bytes must survive immediate abort"; + + let (status, resok) = write_file_sync_result(&context, created_fh, payload).await; + + assert!(matches!(status, nfs::nfsstat3::NFS3_OK)); + let resok = resok.expect("WRITE should return success payload"); + assert!(matches!(resok.committed, stable_how::FILE_SYNC)); + println!( + "FILE_SYNC WRITE replied NFS3_OK count={} committed={:?}; aborting server before batch timer", + resok.count, resok.committed + ); + + drop(context); + drop(fs); + + let reopened = AgentSdk::open(AgentFSOptions::with_path( + db_path.to_str().expect("test DB path is UTF-8"), + )) + .await + .expect("reopen AgentFS after simulated server abort"); + let persisted = read_file(&reopened.fs, "loose-object", payload.len() as u64).await; + println!( + "reopened DB persisted {} bytes after FILE_SYNC abort", + persisted.len() + ); + assert_eq!( + persisted, payload, + "NFS FILE_SYNC reply must not be sent before bytes are durable" + ); + } + + #[tokio::test] + async fn write_wcc_and_getattr_are_coherent_with_acknowledged_data() { + force_long_write_batcher_window(); + let (context, _fs) = test_context().await; + let created_fh = create_readonly_file(&context).await; + let payload = b"coherent attrs after ack"; + + let (status, resok) = write_file_sync_result(&context, created_fh.clone(), payload).await; + + assert!(matches!(status, nfs::nfsstat3::NFS3_OK)); + let resok = resok.expect("WRITE should return success payload"); + assert_eq!(resok.count, payload.len() as u32); + assert!(matches!(resok.committed, stable_how::FILE_SYNC)); + let after = match resok.file_wcc.after { + nfs::post_op_attr::attributes(attr) => attr, + nfs::post_op_attr::Void => panic!("WRITE wcc omitted post-op attrs"), + }; + assert_eq!(after.size, payload.len() as u64); + match resok.file_wcc.before { + nfs::pre_op_attr::attributes(before) => { + assert!( + after.mtime.seconds > before.mtime.seconds + || (after.mtime.seconds == before.mtime.seconds + && after.mtime.nseconds >= before.mtime.nseconds), + "WRITE wcc mtime must be non-decreasing" + ); + } + nfs::pre_op_attr::Void => panic!("WRITE wcc omitted pre-op attrs"), + } + + let (getattr_status, getattr_attr) = getattr_result(&context, created_fh.clone()).await; + assert!(matches!(getattr_status, nfs::nfsstat3::NFS3_OK)); + let getattr_attr = getattr_attr.expect("GETATTR should return attrs"); + assert_eq!(getattr_attr.size, payload.len() as u64); + assert!( + getattr_attr.mtime.seconds > after.mtime.seconds + || (getattr_attr.mtime.seconds == after.mtime.seconds + && getattr_attr.mtime.nseconds >= after.mtime.nseconds), + "GETATTR mtime must not move backwards after WRITE wcc" + ); + + let truncated_size = 7; + let setattr_status = + setattr_size_status(&context, created_fh.clone(), truncated_size).await; + assert!(matches!(setattr_status, nfs::nfsstat3::NFS3_OK)); + let (getattr_status, getattr_attr) = getattr_result(&context, created_fh).await; + assert!(matches!(getattr_status, nfs::nfsstat3::NFS3_OK)); + assert_eq!( + getattr_attr + .expect("GETATTR after SETATTR should return attrs") + .size, + truncated_size + ); + } + #[tokio::test] async fn create_authorized_handle_can_write_after_readonly_final_mode() { let (context, fs) = test_context().await; @@ -3218,6 +3733,28 @@ mod tests { assert_eq!(read_file(&fs, "loose-object", 4).await, b"data"); } + #[tokio::test] + async fn readdirplus_refreshed_handle_can_write_after_readonly_final_mode() { + let (context, fs) = test_context().await; + let created_fh = create_readonly_file(&context).await; + let created_id = context + .vfs + .fh_to_id(&created_fh) + .expect("created handle resolves"); + + let readdirplus_fh = readdirplus_handle_for_name(&context, b"loose-object").await; + + assert!(context + .vfs + .fh_has_write_authority(&readdirplus_fh, created_id)); + let status = write_status(&context, readdirplus_fh.clone(), b"data").await; + assert!(matches!(status, nfs::nfsstat3::NFS3_OK)); + let (getattr_status, getattr_attr) = getattr_result(&context, readdirplus_fh).await; + assert!(matches!(getattr_status, nfs::nfsstat3::NFS3_OK)); + assert_eq!(getattr_attr.expect("attrs").mode, 0o444); + assert_eq!(read_file(&fs, "loose-object", 4).await, b"data"); + } + #[tokio::test] async fn fresh_lookup_handle_without_write_permission_stays_denied() { let (context, fs) = test_context().await; diff --git a/crates/agentfs-nfs/src/server/permissions.rs b/crates/agentfs-nfs/src/server/permissions.rs new file mode 100644 index 00000000..93bd2e97 --- /dev/null +++ b/crates/agentfs-nfs/src/server/permissions.rs @@ -0,0 +1,175 @@ +//! NFS-facing shim over the shared AgentFS access semantics. +//! +//! POSIX permission logic lives in `agentfs_core::semantics::access`. This +//! module only converts NFS wire types to core credentials/stats and maps the +//! shared answers back to RFC 1813 bitmasks/status codes. + +use super::nfs::{ + fattr3, ftype3, nfsstat3, sattr3, set_atime, set_gid3, set_mode3, set_mtime, set_size3, + set_uid3, +}; +use super::rpc::auth_unix; +use agentfs_core::fs::FsError; +use agentfs_core::semantics::access::{self, AttrChange, Credentials, TimeUpdate}; +use agentfs_core::{Stats, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFREG, S_IFSOCK}; +use smallvec::SmallVec; + +/// NFS ACCESS procedure permission bits (from RFC 1813) +pub const ACCESS3_READ: u32 = 0x0001; +pub const ACCESS3_LOOKUP: u32 = 0x0002; +pub const ACCESS3_MODIFY: u32 = 0x0004; +pub const ACCESS3_EXTEND: u32 = 0x0008; +pub const ACCESS3_DELETE: u32 = 0x0010; +pub const ACCESS3_EXECUTE: u32 = 0x0020; + +/// Convert AUTH_UNIX credentials to core access credentials. +pub fn credentials(auth: &auth_unix) -> Credentials { + Credentials { + uid: auth.uid, + gid: auth.gid, + groups: SmallVec::from_slice(&auth.gids), + } +} + +/// Convert NFS attributes to core `Stats` for access checks. +pub fn stats(attr: &fattr3) -> Stats { + Stats { + ino: attr.fileid as i64, + mode: file_type_mode(attr.ftype) | (attr.mode & 0o7777), + nlink: attr.nlink, + uid: attr.uid, + gid: attr.gid, + size: attr.size as i64, + atime: attr.atime.seconds as i64, + mtime: attr.mtime.seconds as i64, + ctime: attr.ctime.seconds as i64, + atime_nsec: attr.atime.nseconds, + mtime_nsec: attr.mtime.nseconds, + ctime_nsec: attr.ctime.nseconds, + rdev: libc::makedev(attr.rdev.specdata1 as _, attr.rdev.specdata2 as _) as u64, + } +} + +/// Convert an NFS setattr payload to the shared access authorization shape. +pub fn attr_change(attr: &sattr3) -> AttrChange { + AttrChange { + size: matches!(attr.size, set_size3::size(_)), + mode: match attr.mode { + set_mode3::mode(mode) => Some(mode), + set_mode3::Void => None, + }, + uid: match attr.uid { + set_uid3::uid(uid) => Some(uid), + set_uid3::Void => None, + }, + gid: match attr.gid { + set_gid3::gid(gid) => Some(gid), + set_gid3::Void => None, + }, + atime: match attr.atime { + set_atime::SET_TO_CLIENT_TIME(_) => TimeUpdate::Explicit, + set_atime::SET_TO_SERVER_TIME => TimeUpdate::Now, + set_atime::DONT_CHANGE => TimeUpdate::Omit, + }, + mtime: match attr.mtime { + set_mtime::SET_TO_CLIENT_TIME(_) => TimeUpdate::Explicit, + set_mtime::SET_TO_SERVER_TIME => TimeUpdate::Now, + set_mtime::DONT_CHANGE => TimeUpdate::Omit, + }, + } +} + +/// Map shared access-denial errors to NFS status codes. +pub fn denial_status(error: &FsError) -> nfsstat3 { + match error { + FsError::OperationNotPermitted => nfsstat3::NFS3ERR_PERM, + FsError::PermissionDenied => nfsstat3::NFS3ERR_ACCES, + _ => nfsstat3::NFS3ERR_ACCES, + } +} + +/// Apply shared chmod normalization to an NFS setattr payload. +pub fn normalize_setattr_mode( + stats: &Stats, + creds: &Credentials, + change: &AttrChange, +) -> Option { + access::normalize_setattr_mode(stats, creds, change) +} + +/// Clear write/chown privilege bits according to shared access semantics. +pub fn without_killpriv(stats: &Stats, creds: &Credentials, mode: u32) -> u32 { + access::without_killpriv(stats, creds, mode) +} + +/// Compute the ACCESS3 result bitmask for the given auth and file attributes. +/// +/// This implements RFC 1813 ACCESS procedure semantics: +/// - ACCESS3_READ: read file data or directory contents +/// - ACCESS3_LOOKUP: search directory entries (execute permission on directories) +/// - ACCESS3_MODIFY: alter existing file/directory data +/// - ACCESS3_EXTEND: add new data or directory entries +/// - ACCESS3_DELETE: remove directory entries (checked against parent directory) +/// - ACCESS3_EXECUTE: execute files (execute permission on files) +pub fn compute_access(auth: &auth_unix, attr: &fattr3, requested: u32) -> u32 { + let mut result = 0u32; + let creds = credentials(auth); + let stats = stats(attr); + + // ACCESS3_READ - read file data or directory contents + if (requested & ACCESS3_READ) != 0 && access::may_read(&stats, &creds) { + result |= ACCESS3_READ; + } + + // ACCESS3_LOOKUP - search directory (execute permission on directories) + if (requested & ACCESS3_LOOKUP) != 0 + && stats.is_directory() + && access::may_search(&stats, &creds) + { + result |= ACCESS3_LOOKUP; + } + + // ACCESS3_MODIFY - alter existing data (write permission) + if (requested & ACCESS3_MODIFY) != 0 && access::may_write(&stats, &creds) { + result |= ACCESS3_MODIFY; + } + + // ACCESS3_EXTEND - add new data (write permission) + if (requested & ACCESS3_EXTEND) != 0 && access::may_write(&stats, &creds) { + result |= ACCESS3_EXTEND; + } + + // ACCESS3_DELETE - for non-directory files, always 0 (per RFC 1813) + // For directories, this would need to check parent directory permissions + // which is handled at the operation level, not here + if (requested & ACCESS3_DELETE) != 0 { + // DELETE permission is checked at operation time against the parent directory + // For the ACCESS procedure, we return 0 for files (per UNIX semantics) + // and the directory's write permission for directories + if stats.is_directory() && access::may_write(&stats, &creds) { + result |= ACCESS3_DELETE; + } + } + + // ACCESS3_EXECUTE - execute files (not directories) + if (requested & ACCESS3_EXECUTE) != 0 + && !stats.is_directory() + && access::may_search(&stats, &creds) + { + result |= ACCESS3_EXECUTE; + } + + result +} + +fn file_type_mode(ftype: ftype3) -> u32 { + match ftype { + ftype3::NF3REG => S_IFREG, + ftype3::NF3DIR => S_IFDIR, + ftype3::NF3BLK => S_IFBLK, + ftype3::NF3CHR => S_IFCHR, + ftype3::NF3LNK => S_IFLNK, + ftype3::NF3SOCK => S_IFSOCK, + ftype3::NF3FIFO => S_IFIFO, + } +} diff --git a/cli/src/nfsserve/portmap.rs b/crates/agentfs-nfs/src/server/portmap.rs similarity index 79% rename from cli/src/nfsserve/portmap.rs rename to crates/agentfs-nfs/src/server/portmap.rs index 8c9c0680..953df654 100644 --- a/cli/src/nfsserve/portmap.rs +++ b/crates/agentfs-nfs/src/server/portmap.rs @@ -1,5 +1,4 @@ // this is just a complete enumeration of everything in the RFC -#![allow(dead_code)] // And its nice to keep the original RFC names and case #![allow(non_camel_case_types)] @@ -18,7 +17,5 @@ pub struct mapping { pub port: u32, } XDRStruct!(mapping, prog, vers, prot, port); -pub const IPPROTO_TCP: u32 = 6; /* protocol number for TCP/IP */ -pub const IPPROTO_UDP: u32 = 17; /* protocol number for UDP/IP */ pub const PROGRAM: u32 = 100000; pub const VERSION: u32 = 2; diff --git a/cli/src/nfsserve/portmap_handlers.rs b/crates/agentfs-nfs/src/server/portmap_handlers.rs similarity index 100% rename from cli/src/nfsserve/portmap_handlers.rs rename to crates/agentfs-nfs/src/server/portmap_handlers.rs diff --git a/cli/src/nfsserve/rpc.rs b/crates/agentfs-nfs/src/server/rpc.rs similarity index 99% rename from cli/src/nfsserve/rpc.rs rename to crates/agentfs-nfs/src/server/rpc.rs index e3125791..fc8dae07 100644 --- a/cli/src/nfsserve/rpc.rs +++ b/crates/agentfs-nfs/src/server/rpc.rs @@ -1,5 +1,4 @@ // this is just a complete enumeration of everything in the RFC -#![allow(dead_code)] // And its nice to keep the original RFC names and case #![allow(non_camel_case_types)] diff --git a/crates/agentfs-nfs/src/server/rpcwire.rs b/crates/agentfs-nfs/src/server/rpcwire.rs new file mode 100644 index 00000000..da563c8d --- /dev/null +++ b/crates/agentfs-nfs/src/server/rpcwire.rs @@ -0,0 +1,551 @@ +use anyhow::anyhow; +use std::io::Cursor; +use std::io::{Read, Write}; +use tracing::{debug, error, trace, warn}; + +use super::context::RPCContext; +use super::rpc::*; +use super::xdr::*; + +use super::mount; +use super::mount_handlers; + +use super::nfs; +use super::nfs_handlers; +use super::transaction_tracker::{TransactionKey, TransactionLookup}; + +use super::portmap; +use super::portmap_handlers; +use tokio::io::AsyncReadExt; +use tokio::io::AsyncWriteExt; +use tokio::io::DuplexStream; +use tokio::sync::mpsc; + +// Information from RFC 5531 +// https://datatracker.ietf.org/doc/html/rfc5531 + +const NFS_ACL_PROGRAM: u32 = 100227; +const NFS_ID_MAP_PROGRAM: u32 = 100270; +const NFS_METADATA_PROGRAM: u32 = 200024; + +async fn handle_rpc( + input: &mut impl Read, + output: &mut impl Write, + mut context: RPCContext, +) -> Result { + let mut recv = rpc_msg::default(); + recv.deserialize(input)?; + let xid = recv.xid; + if let rpc_body::CALL(call) = recv.body { + if let auth_flavor::AUTH_UNIX = call.cred.flavor { + let mut auth = auth_unix::default(); + auth.deserialize(&mut Cursor::new(&call.cred.body))?; + context.auth = auth; + } + + let mut client_verifier = Vec::new(); + call.verf.serialize(&mut client_verifier)?; + let mut procedure_args = Vec::new(); + input.read_to_end(&mut procedure_args)?; + let transaction_key = TransactionKey::new( + xid, + call.prog, + call.vers, + call.proc, + client_verifier, + &procedure_args, + ); + let transaction = context.transaction_tracker.begin(transaction_key); + let transaction_guard = match transaction { + TransactionLookup::New(guard) => guard, + TransactionLookup::InProgress => { + debug!( + "In-progress retransmission detected, xid: {}, client_addr: {}, call: {:?}", + xid, context.client_addr, call + ); + return Ok(false); + } + TransactionLookup::Replay(reply) => { + debug!( + "Replaying cached RPC reply, xid: {}, client_addr: {}, call: {:?}", + xid, context.client_addr, call + ); + output.write_all(reply.as_ref())?; + return Ok(true); + } + }; + + if call.rpcvers != 2 { + warn!("Invalid RPC version {} != 2", call.rpcvers); + let mut reply = Vec::new(); + rpc_vers_mismatch(xid).serialize(&mut reply)?; + transaction_guard.complete(reply.clone()); + output.write_all(&reply)?; + return Ok(true); + } + + let mut handler_output = Vec::new(); + let mut args_cursor = Cursor::new(procedure_args.as_slice()); + let res = { + if call.prog == nfs::PROGRAM { + nfs_handlers::handle_nfs(xid, call, &mut args_cursor, &mut handler_output, &context) + .await + } else if call.prog == portmap::PROGRAM { + portmap_handlers::handle_portmap( + xid, + call, + &mut args_cursor, + &mut handler_output, + &context, + ) + } else if call.prog == mount::PROGRAM { + mount_handlers::handle_mount( + xid, + call, + &mut args_cursor, + &mut handler_output, + &context, + ) + .await + } else if call.prog == NFS_ACL_PROGRAM + || call.prog == NFS_ID_MAP_PROGRAM + || call.prog == NFS_METADATA_PROGRAM + { + trace!("ignoring NFS_ACL packet"); + prog_unavail_reply_message(xid).serialize(&mut handler_output)?; + Ok(()) + } else { + warn!( + "Unknown RPC Program number {} != {}", + call.prog, + nfs::PROGRAM + ); + prog_unavail_reply_message(xid).serialize(&mut handler_output)?; + Ok(()) + } + } + .map(|_| true); + if res.is_ok() { + transaction_guard.complete(handler_output.clone()); + output.write_all(&handler_output)?; + } + res + } else { + error!("Unexpectedly received a Reply instead of a Call"); + Err(anyhow!("Bad RPC Call format")) + } +} + +/// RFC 1057 Section 10 +/// When RPC messages are passed on top of a byte stream transport +/// protocol (like TCP), it is necessary to delimit one message from +/// another in order to detect and possibly recover from protocol errors. +/// This is called record marking (RM). Sun uses this RM/TCP/IP +/// transport for passing RPC messages on TCP streams. One RPC message +/// fits into one RM record. +/// +/// A record is composed of one or more record fragments. A record +/// fragment is a four-byte header followed by 0 to (2**31) - 1 bytes of +/// fragment data. The bytes encode an unsigned binary number; as with +/// XDR integers, the byte order is from highest to lowest. The number +/// encodes two values -- a boolean which indicates whether the fragment +/// is the last fragment of the record (bit value 1 implies the fragment +/// is the last fragment) and a 31-bit unsigned binary value which is the +/// length in bytes of the fragment's data. The boolean value is the +/// highest-order bit of the header; the length is the 31 low-order bits. +/// (Note that this record specification is NOT in XDR standard form!) +async fn read_fragment( + socket: &mut DuplexStream, + append_to: &mut Vec, +) -> Result { + let mut header_buf = [0_u8; 4]; + socket.read_exact(&mut header_buf).await?; + let fragment_header = u32::from_be_bytes(header_buf); + let is_last = (fragment_header & (1 << 31)) > 0; + let length = (fragment_header & ((1 << 31) - 1)) as usize; + trace!("Reading fragment length:{}, last:{}", length, is_last); + let start_offset = append_to.len(); + append_to.resize(append_to.len() + length, 0); + socket.read_exact(&mut append_to[start_offset..]).await?; + trace!( + "Finishing Reading fragment length:{}, last:{}", + length, + is_last + ); + Ok(is_last) +} + +pub async fn write_fragment( + socket: &mut tokio::net::TcpStream, + buf: &[u8], +) -> Result<(), anyhow::Error> { + // TODO: split into many fragments + assert!(buf.len() < (1 << 31)); + // set the last flag + let fragment_header = buf.len() as u32 + (1 << 31); + let header_buf = u32::to_be_bytes(fragment_header); + socket.write_all(&header_buf).await?; + trace!("Writing fragment length:{}", buf.len()); + socket.write_all(buf).await?; + Ok(()) +} + +pub type SocketMessageType = Result, anyhow::Error>; + +/// The Socket Message Handler reads from a TcpStream and spawns off +/// subtasks to handle each message. replies are queued into the +/// reply_send_channel. +#[derive(Debug)] +pub struct SocketMessageHandler { + cur_fragment: Vec, + socket_receive_channel: DuplexStream, + reply_send_channel: mpsc::UnboundedSender, + context: RPCContext, +} + +impl SocketMessageHandler { + /// Creates a new SocketMessageHandler with the receiver for queued message replies + pub fn new( + context: &RPCContext, + ) -> ( + Self, + DuplexStream, + mpsc::UnboundedReceiver, + ) { + let (socksend, sockrecv) = tokio::io::duplex(256000); + let (msgsend, msgrecv) = mpsc::unbounded_channel(); + ( + Self { + cur_fragment: Vec::new(), + socket_receive_channel: sockrecv, + reply_send_channel: msgsend, + context: context.clone(), + }, + socksend, + msgrecv, + ) + } + + /// Reads a fragment from the socket. This should be looped. + pub async fn read(&mut self) -> Result<(), anyhow::Error> { + let is_last = + read_fragment(&mut self.socket_receive_channel, &mut self.cur_fragment).await?; + if is_last { + let fragment = std::mem::take(&mut self.cur_fragment); + let context = self.context.clone(); + let send = self.reply_send_channel.clone(); + tokio::spawn(async move { + let mut write_buf: Vec = Vec::new(); + let mut write_cursor = Cursor::new(&mut write_buf); + let maybe_reply = + handle_rpc(&mut Cursor::new(fragment), &mut write_cursor, context).await; + match maybe_reply { + Err(e) => { + error!("RPC Error: {:?}", e); + let _ = send.send(Err(e)); + } + Ok(true) => { + let _ = std::io::Write::flush(&mut write_cursor); + let _ = send.send(Ok(write_buf)); + } + Ok(false) => { + // do not reply + } + } + }); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::adapter::AgentNFS; + use crate::server::nfs; + use crate::server::nfs_handlers::createmode3; + use crate::server::rpc::{auth_flavor, call_body, opaque_auth, rpc_body, rpc_msg}; + use crate::server::transaction_tracker::{TransactionTracker, DEFAULT_REPLY_CACHE_CAPACITY}; + use crate::server::vfs::NFSFileSystem; + use agentfs_core::{AgentFS as AgentSdk, AgentFSOptions, FileSystem}; + use std::sync::Arc; + + async fn test_context() -> (RPCContext, agentfs_core::fs::AgentFS) { + let agent = AgentSdk::open(AgentFSOptions::ephemeral()) + .await + .expect("open ephemeral AgentFS"); + agent + .fs + .chmod(1, 0o777) + .await + .expect("make root writable to unprivileged test user"); + let fs = agent.fs.clone(); + let nfs = AgentNFS::new(Arc::new(agent.fs)); + let vfs: Arc = Arc::new(nfs); + let context = RPCContext { + local_port: 11111, + client_addr: "127.0.0.1:40001".to_string(), + auth: crate::server::rpc::auth_unix { + stamp: 0, + machinename: b"test".to_vec(), + uid: 1000, + gid: 1000, + gids: vec![1000], + }, + vfs, + export_name: Arc::new("/".to_string()), + transaction_tracker: Arc::new(TransactionTracker::new(DEFAULT_REPLY_CACHE_CAPACITY)), + }; + (context, fs) + } + + fn verifier(body: &[u8]) -> opaque_auth { + opaque_auth { + flavor: auth_flavor::AUTH_NULL, + body: body.to_vec(), + } + } + + fn serialize_guarded_create_call( + xid: u32, + root_fh: nfs::nfs_fh3, + name: &[u8], + verf: opaque_auth, + ) -> Vec { + let mut msg = Vec::new(); + rpc_msg { + xid, + body: rpc_body::CALL(call_body { + rpcvers: 2, + prog: nfs::PROGRAM, + vers: nfs::VERSION, + proc: 8, + cred: opaque_auth::default(), + verf, + }), + } + .serialize(&mut msg) + .expect("serialize RPC header"); + nfs::diropargs3 { + dir: root_fh, + name: name.into(), + } + .serialize(&mut msg) + .expect("serialize CREATE dirops"); + createmode3::GUARDED + .serialize(&mut msg) + .expect("serialize CREATE mode"); + nfs::sattr3 { + mode: nfs::set_mode3::mode(0o644), + ..Default::default() + } + .serialize(&mut msg) + .expect("serialize CREATE attrs"); + msg + } + + fn serialize_getattr_call(xid: u32, fh: nfs::nfs_fh3, verf: opaque_auth) -> Vec { + let mut msg = Vec::new(); + rpc_msg { + xid, + body: rpc_body::CALL(call_body { + rpcvers: 2, + prog: nfs::PROGRAM, + vers: nfs::VERSION, + proc: 1, + cred: opaque_auth::default(), + verf, + }), + } + .serialize(&mut msg) + .expect("serialize RPC header"); + fh.serialize(&mut msg).expect("serialize GETATTR fh"); + msg + } + + async fn run_rpc(payload: &[u8], context: RPCContext) -> Vec { + let mut input = Cursor::new(payload.to_vec()); + let mut output = Vec::new(); + let replied = handle_rpc(&mut input, &mut output, context) + .await + .expect("handle RPC"); + assert!(replied, "completed retransmissions must replay a reply"); + output + } + + fn parse_nfs_status(reply: &[u8], xid: u32) -> nfs::nfsstat3 { + let mut cursor = Cursor::new(reply.to_vec()); + let mut msg = rpc_msg::default(); + msg.deserialize(&mut cursor).expect("deserialize RPC reply"); + assert_eq!(msg.xid, xid); + match msg.body { + rpc_body::REPLY(crate::server::rpc::reply_body::MSG_ACCEPTED( + crate::server::rpc::accepted_reply { + reply_data: crate::server::rpc::accept_body::SUCCESS, + .. + }, + )) => {} + other => panic!("unexpected RPC reply: {other:?}"), + } + let mut status = nfs::nfsstat3::NFS3_OK; + status + .deserialize(&mut cursor) + .expect("deserialize NFS status"); + status + } + + #[tokio::test] + async fn completed_retransmission_replays_cached_reply_without_reexecuting_create() { + let (context, fs) = test_context().await; + let xid = 4100; + let name = b"drc-replay-create"; + let call = serialize_guarded_create_call( + xid, + context.vfs.id_to_fh(1), + name, + verifier(b"client-verifier"), + ); + + let first_reply = run_rpc(&call, context.clone()).await; + assert!(matches!( + parse_nfs_status(&first_reply, xid), + nfs::nfsstat3::NFS3_OK + )); + + let retransmitted_reply = run_rpc(&call, context.clone()).await; + assert_eq!( + first_reply, retransmitted_reply, + "completed retransmission should replay exact cached reply bytes" + ); + assert!(matches!( + parse_nfs_status(&retransmitted_reply, xid), + nfs::nfsstat3::NFS3_OK + )); + assert!( + FileSystem::lookup(&fs, 1, std::str::from_utf8(name).unwrap()) + .await + .expect("lookup created file") + .is_some() + ); + println!( + "replayed=true reconnect=false xid={xid} verifier=client-verifier side_effect_count=1" + ); + } + + #[tokio::test] + async fn drc_key_ignores_reconnect_source_port_churn() { + let (context, _fs) = test_context().await; + let xid = 4200; + let name = b"drc-reconnect-create"; + let call = serialize_guarded_create_call( + xid, + context.vfs.id_to_fh(1), + name, + verifier(b"stable-client-verifier"), + ); + let mut first_context = context.clone(); + first_context.client_addr = "127.0.0.1:40001".to_string(); + let mut reconnected_context = context.clone(); + reconnected_context.client_addr = "127.0.0.1:50002".to_string(); + + let first_reply = run_rpc(&call, first_context).await; + assert!(matches!( + parse_nfs_status(&first_reply, xid), + nfs::nfsstat3::NFS3_OK + )); + + let retransmitted_reply = run_rpc(&call, reconnected_context).await; + assert_eq!( + first_reply, retransmitted_reply, + "source-port churn must not change the DRC key" + ); + assert!(matches!( + parse_nfs_status(&retransmitted_reply, xid), + nfs::nfsstat3::NFS3_OK + )); + println!( + "replayed=true reconnect=true first_port=40001 second_port=50002 xid={xid} verifier=stable-client-verifier side_effect_count=1" + ); + } + + #[tokio::test] + async fn drc_key_separates_cross_procedure_xid_collisions() { + let (context, _fs) = test_context().await; + let xid = 4300; + let verifier = verifier(b"shared-auth-none-verifier"); + let create = serialize_guarded_create_call( + xid, + context.vfs.id_to_fh(1), + b"drc-cross-proc", + verifier.clone(), + ); + let getattr = serialize_getattr_call(xid, context.vfs.id_to_fh(1), verifier); + + let create_reply = run_rpc(&create, context.clone()).await; + assert!(matches!( + parse_nfs_status(&create_reply, xid), + nfs::nfsstat3::NFS3_OK + )); + + let getattr_reply = run_rpc(&getattr, context.clone()).await; + assert_ne!( + create_reply, getattr_reply, + "same xid/verifier across different procedures must not replay cached bytes" + ); + assert!(matches!( + parse_nfs_status(&getattr_reply, xid), + nfs::nfsstat3::NFS3_OK + )); + println!( + "replayed=false collision=cross-procedure xid={xid} key_fields=prog,vers,proc,args_digest" + ); + } + + #[tokio::test] + async fn drc_key_separates_same_procedure_argument_collisions() { + let (context, fs) = test_context().await; + let xid = 4400; + let verifier = verifier(b"shared-auth-none-verifier"); + let first_name = b"drc-create-a"; + let second_name = b"drc-create-b"; + let first = serialize_guarded_create_call( + xid, + context.vfs.id_to_fh(1), + first_name, + verifier.clone(), + ); + let second = + serialize_guarded_create_call(xid, context.vfs.id_to_fh(1), second_name, verifier); + + let first_reply = run_rpc(&first, context.clone()).await; + assert!(matches!( + parse_nfs_status(&first_reply, xid), + nfs::nfsstat3::NFS3_OK + )); + + let second_reply = run_rpc(&second, context.clone()).await; + assert_ne!( + first_reply, second_reply, + "same xid/verifier/procedure with different args must not replay cached bytes" + ); + assert!(matches!( + parse_nfs_status(&second_reply, xid), + nfs::nfsstat3::NFS3_OK + )); + assert!( + FileSystem::lookup(&fs, 1, std::str::from_utf8(first_name).unwrap()) + .await + .expect("lookup first file") + .is_some() + ); + assert!( + FileSystem::lookup(&fs, 1, std::str::from_utf8(second_name).unwrap()) + .await + .expect("lookup second file") + .is_some() + ); + println!( + "replayed=false collision=same-procedure-different-args xid={xid} key_fields=args_digest" + ); + } +} diff --git a/crates/agentfs-nfs/src/server/tcp.rs b/crates/agentfs-nfs/src/server/tcp.rs new file mode 100644 index 00000000..614b2511 --- /dev/null +++ b/crates/agentfs-nfs/src/server/tcp.rs @@ -0,0 +1,444 @@ +use super::context::RPCContext; +use super::rpcwire::*; +use super::transaction_tracker::{TransactionTracker, DEFAULT_REPLY_CACHE_CAPACITY}; +use super::vfs::NFSFileSystem; +use async_trait::async_trait; +use std::net::SocketAddr; +use std::sync::Arc; +use std::{io, net::IpAddr}; +use tokio::io::AsyncWriteExt; +use tokio::net::TcpListener; +use tokio::task::JoinSet; +use tokio_util::sync::CancellationToken; +use tracing::{debug, error}; + +/// A NFS Tcp Connection Handler +pub(crate) struct NFSTcpListener { + listener: TcpListener, + port: u16, + arcfs: Arc, + export_name: Arc, + transaction_tracker: Arc, +} + +pub fn generate_host_ip(hostnum: u16) -> String { + format!( + "127.88.{}.{}", + ((hostnum >> 8) & 0xFF) as u8, + (hostnum & 0xFF) as u8 + ) +} + +/// processes an established socket +async fn process_socket( + mut socket: tokio::net::TcpStream, + context: RPCContext, + shutdown: CancellationToken, +) -> Result<(), anyhow::Error> { + let (mut message_handler, mut socksend, mut msgrecvchan) = SocketMessageHandler::new(&context); + let _ = socket.set_nodelay(true); + + let reader_shutdown = shutdown.clone(); + let reader_task = tokio::spawn(async move { + loop { + tokio::select! { + _ = reader_shutdown.cancelled() => { + break; + } + result = message_handler.read() => { + if let Err(e) = result { + debug!("Message loop broken due to {:?}", e); + break; + } + } + } + } + }); + let result = loop { + tokio::select! { + _ = shutdown.cancelled() => { + break Ok(()); + } + _ = socket.readable() => { + let mut buf = [0; 128000]; + + match socket.try_read(&mut buf) { + Ok(0) => { + break Ok(()); + } + Ok(n) => { + let _ = socksend.write_all(&buf[..n]).await; + } + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + continue; + } + Err(e) => { + debug!("Message handling closed : {:?}", e); + break Err(e.into()); + } + } + + }, + reply = msgrecvchan.recv() => { + match reply { + Some(Err(e)) => { + debug!("Message handling closed : {:?}", e); + break Err(e); + } + Some(Ok(msg)) => { + if let Err(e) = write_fragment(&mut socket, &msg).await { + error!("Write error {:?}", e); + } + } + None => { + break Err(anyhow::anyhow!("Unexpected socket context termination")); + } + } + } + } + }; + + drop(socksend); + match reader_task.await { + Ok(()) => {} + Err(error) => debug!("NFS socket reader task join error: {error:?}"), + } + + result +} + +#[async_trait] +pub(crate) trait NFSTcp: Send + Sync { + /// Gets the true listening port. Useful if the bound port number is 0 + fn get_listen_port(&self) -> u16; + + /// Gets the true listening IP. Useful on windows when the IP may be random + fn get_listen_ip(&self) -> IpAddr; + + /// Handles incoming connections until the cancellation token is triggered. + async fn handle_until_cancelled(&self, shutdown: CancellationToken) -> io::Result<()>; +} + +impl NFSTcpListener { + /// Binds to a ipstr of the form [ip address]:port. For instance + /// "127.0.0.1:12000". fs is an instance of an implementation + /// of NFSFileSystem. + pub async fn bind(ipstr: &str, fs: T) -> io::Result> { + let (ip, port) = ipstr.split_once(':').ok_or_else(|| { + io::Error::new( + io::ErrorKind::AddrNotAvailable, + "IP Address must be of form ip:port", + ) + })?; + let port = port.parse::().map_err(|_| { + io::Error::new( + io::ErrorKind::AddrNotAvailable, + "Port not in range 0..=65535", + ) + })?; + + let arcfs: Arc = Arc::new(fs); + + if ip == "auto" { + let mut num_tries_left = 32; + + for try_ip in 1u16.. { + let ip = generate_host_ip(try_ip); + + let result = NFSTcpListener::bind_internal(&ip, port, arcfs.clone()).await; + + match &result { + Err(_) => { + if num_tries_left == 0 { + return result; + } else { + num_tries_left -= 1; + continue; + } + } + Ok(_) => { + return result; + } + } + } + unreachable!(); // Does not detect automatically that loop above never terminates. + } else { + // Otherwise, try this. + NFSTcpListener::bind_internal(ip, port, arcfs).await + } + } + + async fn bind_internal(ip: &str, port: u16, arcfs: Arc) -> io::Result> { + let ipstr = format!("{ip}:{port}"); + let listener = TcpListener::bind(&ipstr).await?; + debug!("Listening on {:?}", &ipstr); + + let port = match listener.local_addr().unwrap() { + SocketAddr::V4(s) => s.port(), + SocketAddr::V6(s) => s.port(), + }; + Ok(NFSTcpListener { + listener, + port, + arcfs, + export_name: Arc::from("/".to_string()), + transaction_tracker: Arc::new(TransactionTracker::new(DEFAULT_REPLY_CACHE_CAPACITY)), + }) + } +} + +#[async_trait] +impl NFSTcp for NFSTcpListener { + /// Gets the true listening port. Useful if the bound port number is 0 + fn get_listen_port(&self) -> u16 { + let addr = self.listener.local_addr().unwrap(); + addr.port() + } + + fn get_listen_ip(&self) -> IpAddr { + let addr = self.listener.local_addr().unwrap(); + addr.ip() + } + + /// Handles incoming connections until the cancellation token is triggered. + async fn handle_until_cancelled(&self, shutdown: CancellationToken) -> io::Result<()> { + let mut connections = JoinSet::new(); + + loop { + tokio::select! { + _ = shutdown.cancelled() => { + debug!("NFS TCP listener received shutdown signal"); + break; + } + accept_result = self.listener.accept() => { + let (socket, _) = accept_result?; + let context = RPCContext { + local_port: self.port, + client_addr: socket.peer_addr().unwrap().to_string(), + auth: super::rpc::auth_unix::default(), + vfs: self.arcfs.clone(), + export_name: self.export_name.clone(), + transaction_tracker: self.transaction_tracker.clone(), + }; + debug!("Accepting connection from {}", context.client_addr); + debug!("Accepting socket {:?} {:?}", socket, context); + let connection_shutdown = shutdown.clone(); + connections.spawn(async move { + process_socket(socket, context, connection_shutdown).await + }); + } + joined = connections.join_next(), if !connections.is_empty() => { + log_connection_result(joined); + } + } + } + + while let Some(joined) = connections.join_next().await { + log_connection_result(Some(joined)); + } + + self.arcfs.finalize().await.map_err(io::Error::other)?; + + Ok(()) + } +} + +fn log_connection_result( + joined: Option, tokio::task::JoinError>>, +) { + match joined { + Some(Ok(Ok(()))) => {} + Some(Ok(Err(error))) => debug!("NFS connection task exited with error: {error:?}"), + Some(Err(error)) => error!("NFS connection task join error: {error:?}"), + None => {} + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::adapter::AgentNFS; + use crate::server::nfs; + use crate::server::rpc::{self, accept_body, accepted_reply, reply_body, rpc_body, rpc_msg}; + use crate::server::vfs::NFSFileSystem; + use crate::server::xdr::XDR; + use agentfs_core::{AgentFS as AgentSdk, AgentFSOptions, FileSystem, DEFAULT_FILE_MODE}; + use std::io::Cursor; + use std::time::Duration; + use tokio::io::AsyncReadExt; + + fn serialize_rpc_call(xid: u32, proc: u32) -> Vec { + let mut msg = Vec::new(); + rpc_msg { + xid, + body: rpc_body::CALL(rpc::call_body { + rpcvers: 2, + prog: nfs::PROGRAM, + vers: nfs::VERSION, + proc, + cred: rpc::opaque_auth::default(), + verf: rpc::opaque_auth::default(), + }), + } + .serialize(&mut msg) + .expect("serialize RPC call"); + msg + } + + fn parse_rpc_success(payload: Vec, xid: u32) -> Cursor> { + let mut cursor = Cursor::new(payload); + let mut reply = rpc_msg::default(); + reply + .deserialize(&mut cursor) + .expect("deserialize RPC reply"); + assert_eq!(reply.xid, xid); + match reply.body { + rpc_body::REPLY(reply_body::MSG_ACCEPTED(accepted_reply { + reply_data: accept_body::SUCCESS, + .. + })) => {} + other => panic!("unexpected RPC reply: {other:?}"), + } + cursor + } + + async fn read_rpc_payload(stream: &mut tokio::net::TcpStream) -> Vec { + let mut header = [0_u8; 4]; + stream + .read_exact(&mut header) + .await + .expect("read RPC record header"); + let fragment_header = u32::from_be_bytes(header); + assert_ne!( + fragment_header & (1 << 31), + 0, + "reply should be final fragment" + ); + let len = (fragment_header & ((1 << 31) - 1)) as usize; + let mut payload = vec![0_u8; len]; + stream + .read_exact(&mut payload) + .await + .expect("read RPC payload"); + payload + } + + async fn send_null_and_getattr_probe(port: u16, root_fh: nfs::nfs_fh3) { + let mut stream = tokio::net::TcpStream::connect(("127.0.0.1", port)) + .await + .expect("connect to NFS listener"); + + let null_call = serialize_rpc_call(100, 0); + write_fragment(&mut stream, &null_call) + .await + .expect("write NULL call"); + let null_reply = read_rpc_payload(&mut stream).await; + parse_rpc_success(null_reply, 100); + + let mut getattr_call = serialize_rpc_call(101, 1); + root_fh + .serialize(&mut getattr_call) + .expect("serialize GETATTR root file handle"); + write_fragment(&mut stream, &getattr_call) + .await + .expect("write GETATTR call"); + let getattr_reply = read_rpc_payload(&mut stream).await; + let mut cursor = parse_rpc_success(getattr_reply, 101); + let mut status = nfs::nfsstat3::NFS3_OK; + status + .deserialize(&mut cursor) + .expect("deserialize GETATTR status"); + assert!(matches!(status, nfs::nfsstat3::NFS3_OK)); + } + + async fn bind_available_local_port() -> u16 { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0") + .await + .expect("bind ephemeral probe port"); + let port = listener.local_addr().expect("local addr").port(); + drop(listener); + port + } + + #[tokio::test] + async fn cancellation_drains_finalizes_and_releases_port_across_cycles() { + std::env::set_var("AGENTFS_FUSE_WRITEBACK", "1"); + std::env::set_var("AGENTFS_OVERLAY_READS", "1"); + std::env::set_var("AGENTFS_BATCH_MS", "60000"); + std::env::set_var("AGENTFS_BATCH_BYTES", "1048576"); + std::env::set_var("AGENTFS_BATCH_GLOBAL_BYTES", "10485760"); + + let port = bind_available_local_port().await; + + for cycle in 0..3 { + let dir = tempfile::tempdir().expect("tempdir"); + let db_path = dir.path().join(format!("cycle-{cycle}.db")); + let agent = AgentSdk::open(AgentFSOptions::with_path( + db_path.to_str().expect("test DB path is UTF-8"), + )) + .await + .expect("open file-backed AgentFS"); + let (_stats, file) = FileSystem::create_file( + &agent.fs, + 1, + "shutdown-drain.txt", + DEFAULT_FILE_MODE, + 0, + 0, + ) + .await + .expect("create file"); + let payload = format!("cycle-{cycle}-pending-before-shutdown").into_bytes(); + file.pwrite(0, &payload) + .await + .expect("queue pending write before shutdown"); + drop(file); + + let nfs = AgentNFS::new(Arc::new(agent.fs)); + let root_fh = nfs.id_to_fh(1); + let listener = NFSTcpListener::bind(&format!("127.0.0.1:{port}"), nfs) + .await + .expect("bind NFS listener on explicit port"); + let shutdown = CancellationToken::new(); + let server_shutdown = shutdown.clone(); + let server = + tokio::spawn(async move { listener.handle_until_cancelled(server_shutdown).await }); + + send_null_and_getattr_probe(port, root_fh).await; + shutdown.cancel(); + tokio::time::timeout(Duration::from_secs(3), server) + .await + .expect("server should stop within timeout") + .expect("server task should not panic") + .expect("server shutdown should succeed"); + + let rebind = tokio::net::TcpListener::bind(("127.0.0.1", port)).await; + assert!( + rebind.is_ok(), + "cycle {cycle}: cancelled NFS server should release port {port}" + ); + drop(rebind); + + let reopened = AgentSdk::open(AgentFSOptions::with_path( + db_path.to_str().expect("test DB path is UTF-8"), + )) + .await + .expect("reopen DB after NFS shutdown"); + let stats = FileSystem::lookup(&reopened.fs, 1, "shutdown-drain.txt") + .await + .expect("lookup after reopen") + .expect("file exists after reopen"); + let reopened_file = FileSystem::open(&reopened.fs, stats.ino, libc::O_RDONLY) + .await + .expect("open after reopen"); + let persisted = reopened_file + .pread(0, payload.len() as u64) + .await + .expect("read after reopen"); + assert_eq!( + persisted, payload, + "cycle {cycle}: graceful NFS shutdown should drain pending writes" + ); + } + } +} diff --git a/crates/agentfs-nfs/src/server/transaction_tracker.rs b/crates/agentfs-nfs/src/server/transaction_tracker.rs new file mode 100644 index 00000000..c101a7f0 --- /dev/null +++ b/crates/agentfs-nfs/src/server/transaction_tracker.rs @@ -0,0 +1,370 @@ +use std::collections::hash_map::DefaultHasher; +use std::collections::{HashMap, VecDeque}; +use std::hash::{Hash, Hasher}; +use std::sync::{Arc, Mutex}; + +pub const DEFAULT_REPLY_CACHE_CAPACITY: usize = 1024; +pub const DEFAULT_REPLY_CACHE_BYTES: usize = 16 * 1024 * 1024; + +/// `TransactionTracker` is a bounded duplicate-reply cache for RPC transactions. +/// +/// The cache key is the RPC xid, the client's opaque verifier, the RPC +/// program/version/procedure triple, and a compact digest of the procedure +/// arguments. It deliberately excludes the TCP source port so retransmissions +/// after reconnect can replay the original reply instead of re-executing +/// non-idempotent handlers. +pub struct TransactionTracker { + completed_capacity: usize, + completed_byte_capacity: usize, + transactions: Mutex, +} + +impl TransactionTracker { + pub fn new(completed_capacity: usize) -> Self { + Self::with_limits(completed_capacity, DEFAULT_REPLY_CACHE_BYTES) + } + + pub fn with_limits(completed_capacity: usize, completed_byte_capacity: usize) -> Self { + Self { + completed_capacity, + completed_byte_capacity, + transactions: Mutex::new(TransactionCache::default()), + } + } + + /// Begins tracking a transaction. + /// + /// New transactions return a guard. Completing the guard stores the serialized + /// reply bytes. Dropping the guard without completion removes the in-progress + /// entry, which keeps handler panics from blackholing that xid forever. + pub fn begin(self: &Arc, key: TransactionKey) -> TransactionLookup { + let mut cache = self + .transactions + .lock() + .unwrap_or_else(|err| err.into_inner()); + match cache.transactions.get(&key) { + Some(TransactionState::Completed(reply)) => TransactionLookup::Replay(reply.clone()), + Some(TransactionState::InProgress) => TransactionLookup::InProgress, + None => { + cache + .transactions + .insert(key.clone(), TransactionState::InProgress); + TransactionLookup::New(TransactionGuard { + tracker: self.clone(), + key, + completed: false, + }) + } + } + } + + fn complete(&self, key: TransactionKey, reply: Vec) { + let mut cache = self + .transactions + .lock() + .unwrap_or_else(|err| err.into_inner()); + let reply_len = reply.len(); + if reply_len > self.completed_byte_capacity { + cache.remove_in_progress(&key); + return; + } + let previous = cache.transactions.insert( + key.clone(), + TransactionState::Completed(Arc::from(reply.into_boxed_slice())), + ); + match previous { + Some(TransactionState::Completed(previous_reply)) => { + cache.completed_bytes = cache.completed_bytes.saturating_sub(previous_reply.len()); + } + _ => { + cache.completed_count += 1; + } + } + cache.completed_bytes += reply_len; + cache.completed_order.push_back(key); + cache.evict_completed(self.completed_capacity, self.completed_byte_capacity); + } + + fn remove_in_progress(&self, key: &TransactionKey) { + let mut cache = self + .transactions + .lock() + .unwrap_or_else(|err| err.into_inner()); + cache.remove_in_progress(key); + } + + #[cfg(test)] + fn completed_len_for_tests(&self) -> usize { + self.transactions + .lock() + .unwrap_or_else(|err| err.into_inner()) + .completed_count + } + + #[cfg(test)] + fn completed_bytes_for_tests(&self) -> usize { + self.transactions + .lock() + .unwrap_or_else(|err| err.into_inner()) + .completed_bytes + } + + #[cfg(test)] + fn in_progress_len_for_tests(&self) -> usize { + self.transactions + .lock() + .unwrap_or_else(|err| err.into_inner()) + .transactions + .values() + .filter(|state| matches!(state, TransactionState::InProgress)) + .count() + } +} + +pub enum TransactionLookup { + New(TransactionGuard), + InProgress, + Replay(Arc<[u8]>), +} + +pub struct TransactionGuard { + tracker: Arc, + key: TransactionKey, + completed: bool, +} + +impl TransactionGuard { + pub fn complete(mut self, reply: Vec) { + self.tracker.complete(self.key.clone(), reply); + self.completed = true; + } +} + +impl Drop for TransactionGuard { + fn drop(&mut self) { + if !self.completed { + self.tracker.remove_in_progress(&self.key); + } + } +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub(crate) struct TransactionKey { + xid: u32, + prog: u32, + vers: u32, + proc: u32, + client_verifier: Vec, + args_fingerprint: RequestFingerprint, +} + +impl TransactionKey { + pub(crate) fn new( + xid: u32, + prog: u32, + vers: u32, + proc: u32, + client_verifier: Vec, + args: &[u8], + ) -> Self { + Self { + xid, + prog, + vers, + proc, + client_verifier, + args_fingerprint: RequestFingerprint::from_bytes(args), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +struct RequestFingerprint { + len: usize, + hash: u64, +} + +impl RequestFingerprint { + fn from_bytes(bytes: &[u8]) -> Self { + let mut hasher = DefaultHasher::new(); + bytes.hash(&mut hasher); + Self { + len: bytes.len(), + hash: hasher.finish(), + } + } +} + +#[derive(Default)] +struct TransactionCache { + transactions: HashMap, + completed_order: VecDeque, + completed_count: usize, + completed_bytes: usize, +} + +impl TransactionCache { + fn evict_completed(&mut self, completed_capacity: usize, completed_byte_capacity: usize) { + while self.completed_count > completed_capacity + || self.completed_bytes > completed_byte_capacity + { + let Some(key) = self.completed_order.pop_front() else { + break; + }; + if let Some(TransactionState::Completed(reply)) = self.transactions.remove(&key) { + self.completed_count -= 1; + self.completed_bytes = self.completed_bytes.saturating_sub(reply.len()); + } + } + } + + fn remove_in_progress(&mut self, key: &TransactionKey) { + if matches!( + self.transactions.get(key), + Some(TransactionState::InProgress) + ) { + self.transactions.remove(key); + } + } +} + +enum TransactionState { + InProgress, + Completed(Arc<[u8]>), +} + +#[cfg(test)] +mod tests { + use super::*; + use std::panic; + use std::sync::Arc; + + fn verifier(bytes: &[u8]) -> Vec { + bytes.to_vec() + } + + fn key(xid: u32, verifier_bytes: &[u8], proc: u32, args: &[u8]) -> TransactionKey { + TransactionKey::new(xid, 100003, 3, proc, verifier(verifier_bytes), args) + } + + fn complete(tracker: &Arc, xid: u32, verifier_bytes: &[u8], reply: &[u8]) { + match tracker.begin(key(xid, verifier_bytes, 1, b"args")) { + TransactionLookup::New(guard) => guard.complete(reply.to_vec()), + _ => panic!("expected new transaction"), + } + } + + #[test] + fn completed_reply_cache_is_bounded_and_evicts_oldest_completed() { + let tracker = Arc::new(TransactionTracker::new(2)); + + complete(&tracker, 1, b"client", b"reply-1"); + complete(&tracker, 2, b"client", b"reply-2"); + complete(&tracker, 3, b"client", b"reply-3"); + + assert_eq!(tracker.completed_len_for_tests(), 2); + assert!( + matches!( + tracker.begin(key(1, b"client", 1, b"args")), + TransactionLookup::New(_) + ), + "oldest completed reply should be evicted when capacity is exceeded" + ); + assert!( + matches!( + tracker.begin(key(2, b"client", 1, b"args")), + TransactionLookup::Replay(reply) if reply.as_ref() == b"reply-2" + ), + "second completed reply should still be cached" + ); + assert!( + matches!( + tracker.begin(key(3, b"client", 1, b"args")), + TransactionLookup::Replay(reply) if reply.as_ref() == b"reply-3" + ), + "newest completed reply should still be cached" + ); + } + + #[test] + fn in_progress_guard_drop_cleans_up_after_handler_panic() { + let tracker = Arc::new(TransactionTracker::new(2)); + let panicking_tracker = tracker.clone(); + + let original_hook = panic::take_hook(); + panic::set_hook(Box::new(|_| {})); + let result = panic::catch_unwind(move || { + match panicking_tracker.begin(key(42, b"client", 1, b"args")) { + TransactionLookup::New(_guard) => panic!("simulated handler panic"), + _ => panic!("expected new transaction"), + } + }); + panic::set_hook(original_hook); + assert!(result.is_err()); + + assert_eq!(tracker.in_progress_len_for_tests(), 0); + assert!( + matches!( + tracker.begin(key(42, b"client", 1, b"args")), + TransactionLookup::New(_) + ), + "dropped in-progress guard should not blackhole the xid forever" + ); + } + + #[test] + fn key_includes_rpc_shape_and_argument_digest() { + let tracker = Arc::new(TransactionTracker::new(8)); + match tracker.begin(key(7, b"client", 8, b"create-a")) { + TransactionLookup::New(guard) => guard.complete(b"create-a-reply".to_vec()), + _ => panic!("expected first CREATE to be new"), + } + + assert!( + matches!( + tracker.begin(key(7, b"client", 8, b"create-a")), + TransactionLookup::Replay(reply) if reply.as_ref() == b"create-a-reply" + ), + "identical retransmission should replay" + ); + assert!( + matches!( + tracker.begin(key(7, b"client", 1, b"create-a")), + TransactionLookup::New(_) + ), + "same xid/verifier with a different RPC procedure must not replay" + ); + assert!( + matches!( + tracker.begin(key(7, b"client", 8, b"create-b")), + TransactionLookup::New(_) + ), + "same xid/verifier/procedure with different arguments must not replay" + ); + } + + #[test] + fn completed_reply_cache_is_bounded_by_bytes() { + let tracker = Arc::new(TransactionTracker::with_limits(8, 12)); + + complete(&tracker, 1, b"client", b"12345678"); + complete(&tracker, 2, b"client", b"abcdef"); + + assert_eq!(tracker.completed_len_for_tests(), 1); + assert_eq!(tracker.completed_bytes_for_tests(), 6); + assert!( + matches!( + tracker.begin(key(1, b"client", 1, b"args")), + TransactionLookup::New(_) + ), + "oldest reply should be evicted to honor the byte budget" + ); + assert!( + matches!( + tracker.begin(key(2, b"client", 1, b"args")), + TransactionLookup::Replay(reply) if reply.as_ref() == b"abcdef" + ), + "newest reply should remain cached within the byte budget" + ); + } +} diff --git a/cli/src/nfsserve/vfs.rs b/crates/agentfs-nfs/src/server/vfs.rs similarity index 81% rename from cli/src/nfsserve/vfs.rs rename to crates/agentfs-nfs/src/server/vfs.rs index d3f8eead..1c13efc0 100644 --- a/cli/src/nfsserve/vfs.rs +++ b/crates/agentfs-nfs/src/server/vfs.rs @@ -1,13 +1,15 @@ use super::nfs; use super::nfs::*; -pub use super::rpc::auth_unix; +pub(crate) use super::rpc::auth_unix; +use agentfs_core::semantics::AckDurability; use async_trait::async_trait; use std::cmp::Ordering; -use std::sync::Once; +use std::sync::OnceLock; use std::time::SystemTime; #[derive(Default, Debug)] pub struct DirEntrySimple { pub fileid: fileid3, + pub cookie: cookie3, pub name: filename3, } #[derive(Default, Debug)] @@ -19,6 +21,7 @@ pub struct ReadDirSimpleResult { #[derive(Default, Debug)] pub struct DirEntry { pub fileid: fileid3, + pub cookie: cookie3, pub name: filename3, pub attr: fattr3, } @@ -35,6 +38,7 @@ impl ReadDirSimpleResult { .iter() .map(|e| DirEntrySimple { fileid: e.fileid, + cookie: e.cookie, name: e.name.clone(), }) .collect(); @@ -45,25 +49,15 @@ impl ReadDirSimpleResult { } } -static mut GENERATION_NUMBER: u64 = 0; -static GENERATION_NUMBER_INIT: Once = Once::new(); +static GENERATION_NUMBER: OnceLock = OnceLock::new(); fn get_generation_number() -> u64 { - unsafe { - GENERATION_NUMBER_INIT.call_once(|| { - GENERATION_NUMBER = SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis() as u64; - }); - GENERATION_NUMBER - } -} - -/// What capabilities are supported -pub enum VFSCapabilities { - ReadOnly, - ReadWrite, + *GENERATION_NUMBER.get_or_init(|| { + SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64 + }) } /// The basic API to implement to provide an NFS file system @@ -96,8 +90,6 @@ pub enum VFSCapabilities { /// #[async_trait] pub trait NFSFileSystem: Sync { - /// Returns the set of capabilities supported - fn capabilities(&self) -> VFSCapabilities; /// Returns the ID the of the root directory "/" fn root_dir(&self) -> fileid3; /// Look up the id of a path in a directory @@ -113,8 +105,7 @@ pub trait NFSFileSystem: Sync { /// This method should be fast as it is used very frequently. async fn getattr(&self, id: fileid3) -> Result; - /// Sets the attributes of an id - /// this should return Err(nfsstat3::NFS3ERR_ROFS) if readonly + /// Sets the attributes of an id. async fn setattr(&self, id: fileid3, setattr: sattr3) -> Result; /// Reads the contents of a file returning (bytes, EOF) @@ -124,16 +115,18 @@ pub trait NFSFileSystem: Sync { async fn read(&self, id: fileid3, offset: u64, count: u32) -> Result<(Vec, bool), nfsstat3>; - /// Writes the contents of a file returning (bytes, EOF) + /// Writes the contents of a file. /// Note that offset/count may go past the end of the file and that /// in that case, the file is extended. - /// If not supported due to readonly file system - /// this should return Err(nfsstat3::NFS3ERR_ROFS) - async fn write(&self, id: fileid3, offset: u64, data: &[u8]) -> Result; + async fn write( + &self, + id: fileid3, + offset: u64, + data: &[u8], + durability: AckDurability, + ) -> Result<(fattr3, AckDurability), nfsstat3>; /// Creates a file with the following attributes. - /// If not supported due to readonly file system - /// this should return Err(nfsstat3::NFS3ERR_ROFS) /// The auth parameter contains the caller's credentials for ownership. async fn create( &self, @@ -144,7 +137,6 @@ pub trait NFSFileSystem: Sync { ) -> Result<(fileid3, fattr3), nfsstat3>; /// Creates a file if it does not already exist - /// this should return Err(nfsstat3::NFS3ERR_ROFS) /// The auth parameter contains the caller's credentials for ownership. async fn create_exclusive( &self, @@ -154,8 +146,6 @@ pub trait NFSFileSystem: Sync { ) -> Result; /// Makes a directory with the following attributes. - /// If not supported dur to readonly file system - /// this should return Err(nfsstat3::NFS3ERR_ROFS) /// The auth parameter contains the caller's credentials for ownership. async fn mkdir( &self, @@ -166,8 +156,6 @@ pub trait NFSFileSystem: Sync { ) -> Result<(fileid3, fattr3), nfsstat3>; /// Creates a special file (device, fifo, socket) with the given attributes. - /// If not supported due to readonly file system - /// this should return Err(nfsstat3::NFS3ERR_ROFS) /// The auth parameter contains the caller's credentials for ownership. async fn mknod( &self, @@ -180,13 +168,9 @@ pub trait NFSFileSystem: Sync { ) -> Result<(fileid3, fattr3), nfsstat3>; /// Removes a file. - /// If not supported due to readonly file system - /// this should return Err(nfsstat3::NFS3ERR_ROFS) async fn remove(&self, dirid: fileid3, filename: &filename3) -> Result<(), nfsstat3>; /// Removes a file. - /// If not supported due to readonly file system - /// this should return Err(nfsstat3::NFS3ERR_ROFS) async fn rename( &self, from_dirid: fileid3, @@ -196,8 +180,6 @@ pub trait NFSFileSystem: Sync { ) -> Result<(), nfsstat3>; /// Creates a hard link to an existing file. - /// If not supported due to readonly file system - /// this should return Err(nfsstat3::NFS3ERR_ROFS) async fn link( &self, id: fileid3, @@ -207,11 +189,11 @@ pub trait NFSFileSystem: Sync { /// Returns the contents of a directory with pagination. /// Directory listing should be deterministic. - /// Up to max_entries may be returned, and start_after is used - /// to determine where to start returning entries from. + /// Up to max_entries may be returned, and start_after is the opaque cookie + /// returned on the prior page's last entry. /// - /// For instance if the directory has entry with ids [1,6,2,11,8,9] - /// and start_after=6, readdir should returning 2,11,8,... + /// For instance if the directory returns cookies [1,6,2,11,8,9] + /// and start_after=6, readdir should return 2,11,8,... // async fn readdir( &self, @@ -225,16 +207,15 @@ pub trait NFSFileSystem: Sync { async fn readdir_simple( &self, dirid: fileid3, + start_after: fileid3, count: usize, ) -> Result { Ok(ReadDirSimpleResult::from_readdir_result( - &self.readdir(dirid, 0, count).await?, + &self.readdir(dirid, start_after, count).await?, )) } /// Makes a symlink with the following attributes. - /// If not supported due to readonly file system - /// this should return Err(nfsstat3::NFS3ERR_ROFS) /// The auth parameter contains the caller's credentials for ownership. async fn symlink( &self, @@ -258,7 +239,7 @@ pub trait NFSFileSystem: Sync { let res = fsinfo3 { obj_attributes: dir_attr, rtmax: 1024 * 1024, - rtpref: 1024 * 124, + rtpref: 1024 * 1024, rtmult: 1024 * 1024, wtmax: 1024 * 1024, wtpref: 1024 * 1024, @@ -274,6 +255,12 @@ pub trait NFSFileSystem: Sync { Ok(res) } + /// Finalize a clean server shutdown after the accept loop has stopped and + /// all connection tasks have exited. + async fn finalize(&self) -> anyhow::Result<()> { + Ok(()) + } + /// Converts the fileid to an opaque NFS file handle. Optional. fn id_to_fh(&self, id: fileid3) -> nfs_fh3 { let gennum = get_generation_number(); @@ -294,6 +281,15 @@ pub trait NFSFileSystem: Sync { self.id_to_fh(id) } + /// Converts the fileid to a handle suitable for READDIRPLUS. + /// + /// Implementations with live CREATE write-authority tokens can override + /// this to keep a client-side node refresh from downgrading the handle to + /// a plain mode-bit-gated handle. + fn id_to_readdirplus_fh(&self, id: fileid3) -> nfs_fh3 { + self.id_to_fh(id) + } + /// Returns whether this exact opaque file handle carries write authority /// captured at CREATE time for the resolved fileid. fn fh_has_write_authority(&self, _fh: &nfs_fh3, _id: fileid3) -> bool { diff --git a/cli/src/nfsserve/write_counter.rs b/crates/agentfs-nfs/src/server/write_counter.rs similarity index 90% rename from cli/src/nfsserve/write_counter.rs rename to crates/agentfs-nfs/src/server/write_counter.rs index dc9ce4b4..8d4b480e 100644 --- a/cli/src/nfsserve/write_counter.rs +++ b/crates/agentfs-nfs/src/server/write_counter.rs @@ -1,4 +1,3 @@ -#![allow(dead_code)] use std::io::Write; // from https://stackoverflow.com/questions/42187591/how-to-keep-track-of-how-many-bytes-written-when-using-stdiowrite @@ -16,10 +15,6 @@ where WriteCounter { inner, count: 0 } } - pub fn into_inner(self) -> W { - self.inner - } - pub fn bytes_written(&self) -> usize { self.count } diff --git a/cli/src/nfsserve/xdr.rs b/crates/agentfs-nfs/src/server/xdr.rs similarity index 98% rename from cli/src/nfsserve/xdr.rs rename to crates/agentfs-nfs/src/server/xdr.rs index 6ec9dbdd..f58d436c 100644 --- a/cli/src/nfsserve/xdr.rs +++ b/crates/agentfs-nfs/src/server/xdr.rs @@ -14,7 +14,6 @@ pub trait XDR { /// Serializes a basic enumeration. /// Casts everything as u32 BigEndian #[allow(non_camel_case_types)] -#[macro_export] macro_rules! XDREnumSerde { ($t:ident) => { impl XDR for $t { @@ -161,7 +160,6 @@ impl XDR for Vec { } #[allow(non_camel_case_types)] -#[macro_export] macro_rules! XDRStruct { ( $t:ident, @@ -195,7 +193,6 @@ macro_rules! XDRStruct { /// The serde methods can be generated with XDRBoolUnion(pre_op_attr, attributes, wcc_attr) /// The "true" type must have the Default trait #[allow(non_camel_case_types)] -#[macro_export] macro_rules! XDRBoolUnion { ( $t:ident, $enumcase:ident, $enumtype:ty @@ -228,7 +225,3 @@ macro_rules! XDRBoolUnion { } }; } - -pub(crate) use XDRBoolUnion; -pub(crate) use XDREnumSerde; -pub(crate) use XDRStruct; diff --git a/dist-workspace.toml b/dist-workspace.toml index 641e5330..8cba04b8 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -1,5 +1,5 @@ [workspace] -members = ["cargo:cli"] +members = ["cargo:."] # Config for 'dist' [dist] @@ -10,12 +10,10 @@ allow-dirty = ["ci"] # CI backends to support ci = "github" # The installers to generate for each app -installers = ["shell", "powershell"] +installers = ["shell"] # Target platforms to build apps for (Rust target-triple syntax) -targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"] +targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu"] # Path that installers should place binaries in install-path = "CARGO_HOME" # Whether to install an updater program install-updater = true -# Dependencies -github-build-setup = "build-setup.yml" diff --git a/docs/KNOBS.md b/docs/KNOBS.md new file mode 100644 index 00000000..ae87ccfa --- /dev/null +++ b/docs/KNOBS.md @@ -0,0 +1,54 @@ +# AgentFS Runtime Knobs + + + +Every active runtime knob is declared here with an architecture class. Defaults are rendered from the typed config declarations used by the SDK/core and FUSE adapter config modules. + +## Active knobs + +| Name | Surface | Class | Default | Owner | Description | Removal criteria | Gate | +|---|---|---|---|---|---|---|---| +| `AGENTFS_KEY` | env or --key | product-config | `unset` | agentfs CLI edge | Hex-encoded local encryption key for CLI commands that open a database. | n/a | opts clap env binding | +| `AGENTFS_CIPHER` | env or --cipher | product-config | `unset` | agentfs CLI edge | Encryption cipher paired with AGENTFS_KEY or --key. | n/a | opts clap env binding | +| `AGENTFS_CLONE_TIMINGS` | env | sunset | `false` | agentfs CLI edge | Ad hoc clone timing printout for local performance investigations. | Remove after telemetry registry exposes clone timing through the single report sink. | config::clone_timings_enabled | +| `AGENTFS_PROFILE` | env | product-config | `false` | agentfs-core config | Enables profiling counters and summaries. | n/a | VAL-CONF-011 and VAL-CONF-014 | +| `AGENTFS_OVERLAY_READS` | env | sunset | `true` | agentfs-core config | Tier-4 pending-write read overlay rollback path. | Remove after PendingView/stat coherence and overlay read gates are green for N=2 consecutive milestones. | VAL-CORE-006 and phase8 smoke | +| `AGENTFS_KEEPCACHE_DELTA` | env | product-config | `true` | agentfs-core config | Allows DB-backed delta files to participate in keep-cache eligibility. | n/a | VAL-FUSE-014 | +| `AGENTFS_BATCH_MS` | env | product-config | `5` | agentfs-core config | Write batcher timer window in milliseconds. | n/a | sdk write-batcher tests | +| `AGENTFS_BATCH_BYTES` | env | product-config | `4194304` | agentfs-core config | Per-inode pending-byte drain trigger. | n/a | sdk write-batcher tests | +| `AGENTFS_BATCH_GLOBAL_BYTES` | env | product-config | `67108864` | agentfs-core config | Global pending-byte cap across inodes. | n/a | sdk write-batcher tests | +| `AGENTFS_BATCH_TXN_INODES` | env | product-config | `1024` | agentfs-core config | Maximum inodes drained or imported per transaction. | n/a | sdk write-batcher and import tests | +| `AGENTFS_BATCH_TXN_BYTES` | env | product-config | `33554432` | agentfs-core config | Maximum bytes drained or imported per transaction. | n/a | sdk write-batcher and import tests | +| `AGENTFS_DRAIN_ON_SETATTR` | env | sunset | `true` | agentfs-core config | Compatibility path that drains pending writes before setattr operations. | Remove after PendingView/stat coherence and setattr tests are green for N=2 consecutive milestones. | VAL-CORE-006 and VAL-NFS-016 | +| `--partial-origin` | cli flag | product-config | `off` | agentfs CLI edge | First-class partial-origin copy-up policy: off, on, or auto. | n/a | partial_origin::legacy_env_does_not_override_cli_off | +| `--partial-origin-threshold-bytes` | cli flag | product-config | `1048576` | agentfs CLI edge | Size threshold used by --partial-origin auto. | n/a | opts partial-origin parse tests | +| `AGENTFS_FUSE_WORKERS` | env | product-config | `auto` | agentfs FUSE config | FUSE request dispatch mode. auto sizes from CPU and memory budgets; serial disables kernel-cache acceleration for safety. | n/a | fuse_config::tests::workers_config_feeds_dispatch_and_cache_policy | +| `AGENTFS_FUSE_CPU_PERCENT` | env | product-config | `50` | agentfs FUSE config | CPU budget percentage used when AGENTFS_FUSE_WORKERS=auto. | n/a | knobs::tests::knob_defaults_in_docs_match_runtime_defaults | +| `AGENTFS_FUSE_MEMORY_PERCENT` | env | product-config | `50` | agentfs FUSE config | Memory budget percentage used when AGENTFS_FUSE_WORKERS=auto. | n/a | knobs::tests::knob_defaults_in_docs_match_runtime_defaults | +| `AGENTFS_FUSE_QUEUE` | env | product-config | `derived` | agentfs FUSE config | FUSE worker request queue capacity; unset derives from worker count and queue memory budget. | n/a | fuse_config::tests::workers_config_feeds_dispatch_and_cache_policy | +| `AGENTFS_FUSE_QUEUE_MEMORY_PERCENT` | env | product-config | `25` | agentfs FUSE config | Memory budget percentage for derived FUSE queue capacity. | n/a | knobs::tests::knob_defaults_in_docs_match_runtime_defaults | +| `AGENTFS_FUSE_ENTRY_TTL_MS` | env | product-config | `10000` | agentfs FUSE config | Positive dentry TTL in milliseconds. | n/a | knobs::tests::knob_defaults_in_docs_match_runtime_defaults | +| `AGENTFS_FUSE_ATTR_TTL_MS` | env | product-config | `10000` | agentfs FUSE config | Attribute TTL in milliseconds. | n/a | knobs::tests::knob_defaults_in_docs_match_runtime_defaults | +| `AGENTFS_FUSE_NEG_TTL_MS` | env | product-config | `1000` | agentfs FUSE config | Negative dentry TTL in milliseconds. | n/a | knobs::tests::knob_defaults_in_docs_match_runtime_defaults | +| `AGENTFS_FUSE_WRITEBACK` | env | product-config | `true` | agentfs FUSE config | Enables FUSE writeback cache and the SDK write batcher when the CLI opens core config. | n/a | fuse_config::tests::serial_dispatch_disables_kernel_cache_policy | +| `AGENTFS_FUSE_KEEPCACHE` | env | product-config | `true` | agentfs FUSE config | Allows eligible read-only base files to use FOPEN_KEEP_CACHE. | n/a | fuse_config::tests::serial_dispatch_disables_kernel_cache_policy | +| `AGENTFS_FUSE_READDIRPLUS` | env | product-config | `always` | agentfs FUSE config | Kernel READDIRPLUS policy. | n/a | fuse_config::tests::invalid_readdirplus_warns_and_defaults_on | +| `AGENTFS_FUSE_SYNC_INVAL` | env | product-config | `false` | agentfs FUSE config | Requests synchronous kernel invalidation; serial dispatch keeps deferred invalidation for deadlock safety. | n/a | fuse_config::tests::serial_dispatch_disables_kernel_cache_policy | +| `AGENTFS_FUSE_SELF_INVAL` | env | sunset | `false` | agentfs FUSE config | Compatibility path that restores notify-on-self-mutation behavior. | Remove after adapter cache extraction proves self-invalidation suppression through VAL-FUSE cache and coherence gates. | VAL-FUSE-011 and VAL-FUSE-018 | +| `AGENTFS_DRAIN_ON_RELEASE` | env | sunset | `false` | agentfs FUSE config | Compatibility path that restores commit-on-close and disables noopen/noflush. | Remove after N=2 consecutive milestones with VAL-FUSE-015, noopen, and noflush off-leg gates green. | VAL-FUSE-015 | +| `AGENTFS_DRAIN_ON_FORGET` | env | sunset | `false` | agentfs FUSE config | Compatibility path that restores drain-on-forget. | Remove after lifecycle and FORGET-driven cleanup gates are green for N=2 consecutive milestones. | VAL-FUSE-003 and VAL-FUSE-004 | +| `AGENTFS_FUSE_FLUSH_INVAL` | env | sunset | `false` | agentfs FUSE config | Compatibility path that restores invalidate-on-every-FLUSH. | Remove after noflush coherence gates are green for N=2 consecutive milestones. | VAL-FUSE-007 and VAL-FUSE-008 | +| `AGENTFS_FUSE_NOFLUSH` | env | kill-switch | `true` | agentfs FUSE config | Disables close-time FLUSH by returning ENOSYS after the kernel has written dirty pages. | Retire only after N=2 consecutive milestones with both FUSE transport legs green and the named off-leg gate still passing. | VAL-FUSE-007 default leg and VAL-FUSE-008 off leg | +| `AGENTFS_FUSE_NOOPEN` | env | kill-switch | `true` | agentfs FUSE config | Disables per-file OPEN/RELEASE by returning ENOSYS when the kernel supports no-open. | Retire only after N=2 consecutive milestones with both FUSE transport legs green and the named off-leg gate still passing. | VAL-FUSE-003 default leg and VAL-FUSE-004 off leg | +| `AGENTFS_FUSE_INO_FILES_CAP` | env | product-config | `65536` | agentfs FUSE config | Soft cap for inode-file cache backing no-open file resolution. | n/a | scripts/validation/noopen-coherence.py | +| `AGENTFS_FUSE_CACHE_DIR` | env | product-config | `true` | agentfs FUSE config | Directory-entry cache fast path, effective only when keepcache remains enabled. | n/a | VAL-FUSE-011 and VAL-FUSE-018 | +| `AGENTFS_FUSE_STICKY_KEEPCACHE_DROP` | env | sunset | `false` | agentfs FUSE config | Compatibility path that restores old sticky keepcache-drop behavior after mutation. | Remove after keep-cache fingerprint gates are green for N=2 consecutive milestones. | VAL-FUSE-012 and VAL-FUSE-014 | +| `AGENTFS_FUSE_URING` | env | kill-switch | `true` | agentfs FUSE config | FUSE-over-io_uring transport attempt. Set false to force the legacy /dev/fuse path. | Retire only after N=2 consecutive milestones with both FUSE transport legs green and the named off-leg gate still passing. | VAL-FUSE-009 uring leg and VAL-FUSE-010 off leg | +| `AGENTFS_FUSE_URING_DEPTH` | env | product-config | `4` | agentfs FUSE config | io_uring queue depth, bounded by the adapter parser. | n/a | VAL-FUSE-009 | +| `AGENTFS_FUSE_URING_SPIN_US` | env | sunset | `0` | agentfs FUSE config | Compatibility tuning for io_uring busy-poll spin before blocking. | Remove after uring teardown and performance gates are green for N=2 consecutive milestones. | VAL-FUSE-009 and VAL-GATE-004 | + +## Deleted compatibility knobs + +| Name | Surface | Class | Default | Owner | Description | Removal criteria | Gate | +|---|---|---|---|---|---|---|---| +| `AGENTFS_OVERLAY_PARTIAL_ORIGIN` | deleted env compat | sunset | `removed` | agentfs-core config | Removed legacy env compatibility path superseded by --partial-origin. | Already removed in M3. Do not reintroduce; use --partial-origin or --partial-origin-threshold-bytes. | partial_origin::legacy_env_does_not_override_cli_off | diff --git a/docs/MANUAL.md b/docs/MANUAL.md new file mode 100644 index 00000000..6230c354 --- /dev/null +++ b/docs/MANUAL.md @@ -0,0 +1,653 @@ +# AgentFS Reference Guide + +Command-line reference for the AgentFS CLI. + +The command and option sections between the `GENERATED COMMAND REFERENCE` +markers are rendered from the CLI's clap definitions and checked by +`docs::tests::manual_help_parity`, so this manual always matches +`agentfs --help`. + +## Installation + +Build from source (Linux is the first-tier platform; macOS is second-tier, +NFS mount only): + +```bash +cargo +nightly build --release --workspace --bins +install -m 0755 target/release/agentfs ~/.local/bin/ +``` + + + + +## Commands + +Every section below is generated from the clap definitions the binary actually parses; `agentfs --help` and this reference cannot disagree. + +### agentfs completions + +Manage shell completions (supported shells: bash, zsh, fish, elvish, powershell) + +``` +agentfs completions +``` + +#### agentfs completions install + +Install shell completions to your shell rc file + +``` +agentfs completions install [SHELL] +``` + +**Arguments:** + +- `[SHELL]` — Shell to install completions for (defaults to current shell) [possible values: bash, zsh, fish, elvish, power-shell] + +#### agentfs completions uninstall + +Uninstall shell completions from your shell rc file + +``` +agentfs completions uninstall [SHELL] +``` + +**Arguments:** + +- `[SHELL]` — Shell to uninstall completions for (defaults to current shell) [possible values: bash, zsh, fish, elvish, power-shell] + +#### agentfs completions show + +Print instructions for manual installation + +``` +agentfs completions show +``` + +### agentfs init + +Initialize a new agent filesystem + +``` +agentfs init [OPTIONS] [ID] +``` + +**Arguments:** + +- `[ID]` — Agent identifier (if not provided, generates a unique one) + +**Options:** + +- `--force` — Overwrite existing file if it exists +- `--base ` — Base directory for overlay filesystem (copy-on-write) +- `--key ` — Hex-encoded encryption key. Enables local encryption when provided [env: AGENTFS_KEY] +- `--cipher ` — Cipher algorithm for encryption (required with --key). Options: aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4, aes128gcm, aes256gcm [env: AGENTFS_CIPHER] +- `-c, --command ` — Command to execute after initialization (mounts the filesystem, runs command, unmounts) +- `--backend ` — Backend to use for mounting when using -c (default: fuse on Linux, nfs on macOS) [possible values: fuse, nfs; default: fuse] +- `--sync-remote-url ` +- `--sync-partial-prefetch ` [possible values: true, false] +- `--sync-partial-segment-size ` +- `--sync-partial-bootstrap-query ` +- `--sync-partial-bootstrap-length ` + +### agentfs sync + +Remote sync operations + +``` +agentfs sync +``` + +**Arguments:** + +- `` — Agent ID or database path + +#### agentfs sync pull + +Pull remote changes (only of agentfs was initialized with remote sync) + +``` +agentfs sync pull +``` + +#### agentfs sync push + +Push remote changes (only of agentfs was initialized with remote sync) + +``` +agentfs sync push +``` + +#### agentfs sync stats + +Print synced database stats + +``` +agentfs sync stats +``` + +#### agentfs sync checkpoint + +Checkpoint local synced db + +``` +agentfs sync checkpoint +``` + +### agentfs fs + +Filesystem operations + +``` +agentfs fs [OPTIONS] +``` + +**Arguments:** + +- `` — Agent ID or database path + +**Options:** + +- `--key ` — Hex-encoded encryption key for encrypted databases [env: AGENTFS_KEY] +- `--cipher ` — Cipher algorithm for encryption (required with --key). Options: aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4, aes128gcm, aes256gcm [env: AGENTFS_CIPHER] + +#### agentfs fs ls + +List files in the filesystem + +``` +agentfs fs ls [FS_PATH] +``` + +**Arguments:** + +- `[FS_PATH]` — Path to list (default: /) [default: /] + +#### agentfs fs cat + +Display file contents + +``` +agentfs fs cat +``` + +**Arguments:** + +- `` — Path to the file in the filesystem + +#### agentfs fs write + +Write file content + +``` +agentfs fs write +``` + +**Arguments:** + +- `` — Path to the file in the filesystem +- `` — Content of the file + +### agentfs run + +Run a command in the sandboxed environment. + +By default, uses FUSE+overlay with Linux user and mount namespaces for isolation. The overlay uses the host filesystem as a read-only base and stores all changes in an AgentFS-backed delta layer. On macOS the overlay is mounted over NFS and a generated Seatbelt profile scopes writes to the sandbox and reads to the allowed directories plus required platform paths (see the Sandboxing section of docs/MANUAL.md). + +``` +agentfs run [OPTIONS] [COMMAND] [ARGS]... +``` + +**Arguments:** + +- `[COMMAND]` — Command to execute (defaults to bash on Linux, zsh on macOS) +- `[ARGS]...` — Arguments for the command + +**Options:** + +- `--allow ` — Allow read/write access to additional directories (can be specified multiple times) +- `--no-default-allows` — Disable default allowed directories (~/.config, ~/.cache, ~/.local, ~/.claude, etc.) +- `--session ` — Session identifier for sharing delta layer across multiple runs. If not provided, a unique session ID is generated for each run. Use the same session ID to share the delta layer between runs +- `--system` — Allow other system users to access this mount (requires /etc/fuse.conf user_allow_other; use cautiously) +- `--partial-origin ` — Partial-origin policy for base-file writes: off, on, or auto [possible values: off, on, auto] +- `--partial-origin-threshold-bytes ` — Size threshold for --partial-origin auto +- `--key ` — Hex-encoded encryption key for the delta layer. Enables local encryption when provided [env: AGENTFS_KEY] +- `--cipher ` — Cipher algorithm for encryption (required with --key). Options: aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4, aes128gcm, aes256gcm [env: AGENTFS_CIPHER] + +### agentfs exec + +Execute a command with an AgentFS filesystem mounted. + +Mounts the specified AgentFS to a temporary directory, runs the command with that directory as the working directory, then automatically unmounts. This is useful for running tools that need filesystem access without a persistent mount. + +``` +agentfs exec [OPTIONS] [ARGS]... +``` + +**Arguments:** + +- `` — Agent ID or database path +- `` — Command to execute +- `[ARGS]...` — Arguments for the command + +**Options:** + +- `--backend ` — Backend to use for mounting (default: fuse on Linux, nfs on macOS) [possible values: fuse, nfs; default: fuse] +- `--key ` — Hex-encoded encryption key for encrypted databases [env: AGENTFS_KEY] +- `--cipher ` — Cipher algorithm for encryption (required with --key) [env: AGENTFS_CIPHER] + +### agentfs clone + +Clone a git repository into an AgentFS database (fast bulk ingest). + +Runs `git clone --no-checkout` through a temporary mount (pack files are large sequential writes), then materializes the worktree by bulk-importing blobs straight into the database in large transactions and fabricating a matching git index, skipping the per-file FUSE round trips of a regular checkout. The resulting repository lives entirely inside the database; nothing is written to the host filesystem. Submodules and smudge/clean filters are not supported. + +``` +agentfs clone [OPTIONS] [NAME] +``` + +**Arguments:** + +- `` — Agent ID or database path (created if it does not exist) +- `` — Git repository to clone (URL or local path) +- `[NAME]` — Directory name for the repository inside the filesystem (default: derived from the source) + +**Options:** + +- `--backend ` — Backend to use for mounting (default: fuse on Linux, nfs on macOS) [possible values: fuse, nfs; default: fuse] +- `--verify` — Verify `git status` is clean through the mount before finishing + +### agentfs mount + +Mount an agent filesystem using FUSE (or list mounts if no args) + +``` +agentfs mount [OPTIONS] [ID_OR_PATH] [MOUNTPOINT] +``` + +**Arguments:** + +- `[ID_OR_PATH]` — Agent ID or database path (if omitted, lists current mounts) +- `[MOUNTPOINT]` — Mount point directory + +**Options:** + +- `-a, --auto-unmount` — Automatically unmount on exit +- `--allow-root` — Allow root user to access filesystem +- `--system` — Allow other system users to access this mount (requires /etc/fuse.conf user_allow_other; use cautiously) +- `-f, --foreground` — Run in foreground (don't daemonize) +- `--uid ` — User ID to report for all files (defaults to current user) +- `--gid ` — Group ID to report for all files (defaults to current group) +- `--backend ` — Backend to use for mounting [possible values: fuse, nfs; default: fuse] +- `--partial-origin ` — Partial-origin policy for base-file writes: off, on, or auto [possible values: off, on, auto] +- `--partial-origin-threshold-bytes ` — Size threshold for --partial-origin auto +- `--key ` — Hex-encoded encryption key for encrypted databases [env: AGENTFS_KEY] +- `--cipher ` — Cipher algorithm for encryption (required with --key). Options: aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4, aes128gcm, aes256gcm [env: AGENTFS_CIPHER] + +### agentfs diff + +Show differences between base filesystem and delta (overlay mode only) + +``` +agentfs diff +``` + +**Arguments:** + +- `` — Agent ID or database path + +### agentfs timeline + +Display agent action timeline from tool call audit log + +``` +agentfs timeline [OPTIONS] +``` + +**Arguments:** + +- `` — Agent ID or database path + +**Options:** + +- `--limit ` — Limit number of entries to display [default: 100] +- `--filter ` — Filter by tool name +- `--status ` — Filter by status (pending/success/error) [possible values: pending, success, error] +- `--format ` — Output format [possible values: table, json; default: table] + +### agentfs nfs + +Start an NFS server to export an AgentFS filesystem over the network (deprecated: use `agentfs serve nfs` instead) + +``` +agentfs nfs [OPTIONS] +``` + +**Arguments:** + +- `` — Agent ID or database path + +**Options:** + +- `--bind ` — IP address to bind to [default: 127.0.0.1] +- `--port ` — Port to listen on [default: 11111] +- `--key ` — Hex-encoded encryption key for encrypted databases [env: AGENTFS_KEY] +- `--cipher ` — Cipher algorithm for encryption (required with --key). Options: aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4, aes128gcm, aes256gcm [env: AGENTFS_CIPHER] + +### agentfs mcp-server + +Start an MCP server exposing filesystem and KV-store tools (deprecated: use `agentfs serve mcp` instead) + +``` +agentfs mcp-server [OPTIONS] +``` + +**Arguments:** + +- `` — Agent ID or database path + +**Options:** + +- `--tools ` — Tools to expose (comma-separated). If not provided, all tools are exposed. Available tools: read_file, write_file, readdir, mkdir, remove, rename, stat, access, kv_get, kv_set, kv_delete, kv_list + +### agentfs serve + +Serve an AgentFS filesystem via different protocols + +``` +agentfs serve +``` + +#### agentfs serve nfs + +Start an NFS server to export an AgentFS filesystem over the network + +``` +agentfs serve nfs [OPTIONS] +``` + +**Arguments:** + +- `` — Agent ID or database path + +**Options:** + +- `--bind ` — IP address to bind to [default: 127.0.0.1] +- `--port ` — Port to listen on [default: 11111] +- `--key ` — Hex-encoded encryption key for encrypted databases [env: AGENTFS_KEY] +- `--cipher ` — Cipher algorithm for encryption (required with --key). Options: aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4, aes128gcm, aes256gcm [env: AGENTFS_CIPHER] + +#### agentfs serve mcp + +Start an MCP server exposing filesystem and KV-store tools + +``` +agentfs serve mcp [OPTIONS] +``` + +**Arguments:** + +- `` — Agent ID or database path + +**Options:** + +- `--tools ` — Tools to expose (comma-separated). If not provided, all tools are exposed. Available tools: read_file, write_file, readdir, mkdir, remove, rename, stat, access, kv_get, kv_set, kv_delete, kv_list + +### agentfs ps + +List active agentfs run sessions + +``` +agentfs ps +``` + +### agentfs prune + +Prune unused resources + +``` +agentfs prune +``` + +#### agentfs prune mounts + +Unmount unused agentfs mount points + +``` +agentfs prune mounts [OPTIONS] +``` + +**Options:** + +- `--force` — Skip confirmation prompt and unmount immediately + +### agentfs integrity + +Check a local AgentFS database for SQLite and schema corruption + +``` +agentfs integrity [OPTIONS] +``` + +**Arguments:** + +- `` — Agent ID or database path + +**Options:** + +- `--json` — Emit machine-readable JSON +- `--require-portable` — Fail if the database depends on external partial-origin base files +- `--check-base` — Validate partial-origin base file fingerprints against the current base tree +- `--checkpoint` — Checkpoint the WAL and remove empty SQLite sidecars after checks pass +- `--key ` — Hex-encoded encryption key for encrypted databases +- `--cipher ` — Encryption cipher (required with --key) + +### agentfs backup + +Create a portable local AgentFS database backup + +``` +agentfs backup [OPTIONS] +``` + +**Arguments:** + +- `` — Agent ID or database path +- `` — Target database path to create + +**Options:** + +- `--verify` — Reopen and verify the copied main database +- `--materialize` — Materialize partial-origin files into a portable backup +- `--key ` — Hex-encoded encryption key for encrypted databases +- `--cipher ` — Encryption cipher (required with --key) + +### agentfs materialize + +Create a portable database by materializing partial-origin files + +``` +agentfs materialize [OPTIONS] +``` + +**Arguments:** + +- `` — Agent ID or database path + +**Options:** + +- `--output ` — Target database path to create +- `--verify` — Reopen and verify the materialized database +- `--key ` — Hex-encoded encryption key for encrypted databases +- `--cipher ` — Encryption cipher (required with --key) + +### agentfs migrate + +Migrate database schema to the current version + +``` +agentfs migrate [OPTIONS] +``` + +**Arguments:** + +- `` — Agent ID or database path + +**Options:** + +- `--dry-run` — Preview migration without applying changes +- `--copy ` — Copy-migrate into a new database file at this path instead of migrating in place +- `--verify` — Verify migrated state equivalence (requires --copy) +- `--overwrite-target` — Allow replacing an existing --copy target database +- `--key ` — Hex-encoded encryption key for encrypted databases +- `--cipher ` — Encryption cipher (required with --key) + + + +## MCP Server (`agentfs serve mcp`) + +The `write_file` tool overwrites existing files in place and keeps their +mode; files it creates get the default mode `0644` (`rw-r--r--`). + +## Sandboxing (`agentfs run`) + +`agentfs run` scopes both writes and reads at the OS level; the mechanism +differs by platform. + +**Linux (first-tier):** FUSE + overlay inside user and mount namespaces. +Writes land only in the copy-on-write overlay and the allowed directories. +Reads are scoped by hiding the home directory and temp dirs behind +namespace-private tmpfs, re-exposing only the overlay cwd and the allowed +paths; all other system paths are remounted read-only. Writes to those +hidden non-allowed paths land in an ephemeral session-private view (the +namespace tmpfs): the host is protected, but that view is not persisted — +files written there vanish when the session exits and are not visible when +the session is resumed with `--session`. + +**macOS (second-tier):** NFS mount + a generated `sandbox-exec` (Seatbelt) +profile. Writes are restricted to the mountpoint, temp directories, +`~/Library`, and the allowed paths. Reads are default-deny: only the session +directory (`~/.agentfs/run/`), the allowed directories (the defaults plus +`--allow`), and a curated set of platform roots are readable (system +frameworks and libraries, the dyld shared cache cryptex, executable +directories, `/private/etc`, terminfo/locale data under `/usr/share`, temp +directories, `/dev` essentials, `/opt`, `/usr/local`, and `/Applications`). +Ancestors of readable roots are stat-able (metadata only) so path resolution +works. Notable consequences: + +- The rest of your home directory, including `~/Library` and credential + stores such as `~/.ssh` or `~/.aws`, is unreadable unless granted with + `--allow`. (`~/Library` remains writable for Keychain and preferences + compatibility, but is not readable.) +- Tools that need read access outside the workspace must be granted it + explicitly with `--allow `, which grants read and write access, + matching Linux. + +CI covers the macOS read posture only via unit tests that pin the generated +profile; the runtime behavior is verified by the manual macOS release gate +(`scripts/validation/macos-nfs-git-validation.sh`), which includes a +read-scoping leg: a secret outside the allow list must be unreadable, and +`--allow` must make it readable. See [docs/TESTING.md](TESTING.md). + +## Runtime Knobs and Environment Variables + +Every runtime knob (env var or first-class flag) is declared in the generated +[docs/KNOBS.md](KNOBS.md) ledger with its class, default, owner, and gate. +`AGENTFS_KEY` / `AGENTFS_CIPHER` provide default encryption credentials for +the commands whose `--key` / `--cipher` options declare them (see the +generated sections above); `TURSO_DB_AUTH_TOKEN` authenticates cloud sync. + +### FUSE-over-io_uring and rapid remounts + +On Linux kernels with `fuse.enable_uring=1` (the `AGENTFS_FUSE_URING` knob +controls whether AgentFS uses the transport), the kernel drains a just-closed +FUSE connection for roughly two seconds, and a new mount racing that drain can +block inside `mount(2)` indefinitely (observed on kernel 7.1.2). AgentFS +bounds this: the mount is retried for a few seconds and then fails with a +clear error instead of hanging. If rapid unmount-then-mount cycles keep +hitting the error, wait a couple of seconds between cycles or set +`AGENTFS_FUSE_URING=0` on the mount-owning processes. A mount left wedged by +other tooling can be recovered with +`echo 1 > /sys/fs/fuse/connections//abort` (verify the connection id +first). + +### Temp files (`TMPDIR`) + +The `turso_core` dependency (0.5.3) leaks `tursodb-ephemeral-*` sort-spill +files into the temp dir and never unlinks them (`vdbe/execute.rs:10096`). The +CLI therefore points its own `TMPDIR` at a private per-process directory that +is removed on exit, so hosts do not accumulate spill litter. This override is +process-internal: commands spawned by `agentfs run`, `agentfs exec`, and +`agentfs init -c` see the original `TMPDIR`. Stale spill directories from +`SIGKILL`ed processes are garbage-collected on the next CLI start. + +Variables set inside an `agentfs run` sandbox: + +| Variable | Description | +|----------|-------------| +| `AGENTFS` | Set to `1` inside the AgentFS sandbox | +| `AGENTFS_SANDBOX` | Sandbox type: `linux-namespace` or `macos-sandbox` | +| `AGENTFS_SESSION` | Current session ID | + +## Local Encryption + +AgentFS supports encrypting the local SQLite database at rest. + +**Supported ciphers:** + +- `aes256gcm` - AES-256-GCM (requires 64-character hex key) +- `aes128gcm` - AES-128-GCM (requires 32-character hex key) +- `aegis256` - AEGIS-256 (requires 64-character hex key) +- `aegis128l` - AEGIS-128L (requires 32-character hex key) +- `aegis128x2`, `aegis128x4`, `aegis256x2`, `aegis256x4` - AEGIS variants + +**Example: Create an encrypted filesystem** + +```bash +# Generate a 256-bit key (64 hex characters) +KEY=$(openssl rand -hex 32) + +# Initialize with encryption +agentfs init --key $KEY --cipher aes256gcm my-secure-agent + +# Access the filesystem +agentfs fs my-secure-agent --key $KEY --cipher aes256gcm ls / +``` + +**Example: Encrypted sandbox session** + +```bash +agentfs run --key $KEY --cipher aes256gcm -- bash +``` + +**Using environment variables:** + +```bash +export AGENTFS_KEY=$(openssl rand -hex 32) +export AGENTFS_CIPHER=aes256gcm + +agentfs init my-secure-agent +agentfs fs my-secure-agent ls / +``` + +**Limitations:** + +- Local encryption cannot be used with cloud sync (`--sync-remote-url`) + +## Files + +- `.agentfs/.db` - Agent filesystem database (relative to the working + directory where `agentfs init` ran) +- `~/.agentfs/run/` - `agentfs run` session state (listed by `agentfs ps`) + +## Unmounting + +- Linux (FUSE): `fusermount3 -u ` (or `fusermount -u`) +- macOS (NFS): `umount ` + +## See Also + +- [Agent Filesystem Specification](SPEC.md) - SQLite schema specification +- [Runtime Knobs](KNOBS.md) - generated knob ledger +- [Testing](TESTING.md) - validation gates, benchmarks, and the manual + macOS release gate diff --git a/SPEC.md b/docs/SPEC.md similarity index 80% rename from SPEC.md rename to docs/SPEC.md index 3f825cec..a806794e 100644 --- a/SPEC.md +++ b/docs/SPEC.md @@ -4,7 +4,7 @@ ## Introduction -The Agent Filesystem Specification defines a SQLite schema for representing agent filesystem state. The current v0.5 format adds 64 KiB default chunks, inline storage for dense files at or below 4 KiB, and copy-only migration from v0.4 databases. The specification consists of three main components: +The Agent Filesystem Specification defines a SQLite schema for representing agent filesystem state. The current v0.5 format adds 64 KiB default chunks, inline storage for dense files at or below 4 KiB, and `user_version`-keyed migration from older databases (in place by default, or copy-based re-chunking with `--copy`). The specification consists of three main components: 1. **Tool Call Audit Trail**: Captures tool invocations, parameters, and results for debugging, auditing, and performance analysis 2. **Virtual Filesystem**: Stores agent artifacts (files, documents, outputs) using a Unix-like inode design with support for hard links, proper metadata, and efficient file operations @@ -39,6 +39,80 @@ replying; later `FORGET` requests must release the same reference count. Namespace mutations MUST invalidate affected cached dentries and attributes before the mutation is considered visible to the caller. +The subsections below describe the acceleration structures the reference +implementation actually ships and the invariants each one must preserve. +Every one of them has a declared kill switch or policy knob in the generated +knob ledger (`docs/KNOBS.md`). + +### Write Batching and Durability + +Writes MAY be acknowledged from an in-memory pending map before their bytes +reach SQLite. The reference implementation batches FUSE writeback-cache +writes and drains them on a short timer window, a per-inode pending-byte +trigger, a global pending-byte cap, and bounded per-transaction inode/byte +budgets (`AGENTFS_BATCH_*` knobs). Buffered acknowledgement is only permitted +for volatile-durability writes; any operation that promises durability — +`fsync`, an NFSv3 `WRITE` acknowledged as `FILE_SYNC`, or unmount/shutdown +finalization — MUST NOT return until the affected pending bytes are committed +to the database (a per-inode or filesystem-wide commit barrier). + +Pending state is an acceleration structure, never a second authority: +metadata reads (`stat`, directory attributes) MUST merge pending sizes and +times so applications observe their own buffered writes, and deletions MUST +discard the dead inode's pending bytes. On unclean termination, volatile +(never-fsynced) bytes MAY be lost, but the database MUST remain consistent: +a crash may lose the tail of un-synced data, never corrupt committed state. + +### FUSE no-open / no-flush Lifecycle + +On Linux the FUSE adapter runs with zero-message opens and zero-message +flush by default: it answers `OPEN`/`RELEASE` (and close-time `FLUSH`) with +`ENOSYS`, after which the kernel stops sending them and issues data requests +with `fh = 0`. Implementations using this mode MUST NOT key any state to +open/release pairs. Per-inode resources (backing file handles for base reads, +caches) are keyed by inode number, held in a bounded LRU +(`AGENTFS_FUSE_INO_FILES_CAP`), and reclaimed by kernel `FORGET` traffic and +LRU eviction rather than by `RELEASE`. + +Consequences that MUST hold: + +1. Unlink-while-open works through lookup-reference accounting: an unlinked + inode's rows are reaped only after the kernel drops its last lookup + reference (deferred reap), not at `RELEASE` time. +2. Close does not imply commit. With no-flush enabled the kernel has already + written back dirty pages before close; durability is promised only by + `fsync` (see the durability contract above). +3. Both behaviors retain kill switches (`AGENTFS_FUSE_NOOPEN`, + `AGENTFS_FUSE_NOFLUSH`) and dedicated coherence gates + (`scripts/validation/noopen-coherence.py`, + `scripts/validation/flush-coherence.py`) that validate the default and + disabled legs. + +### FUSE-over-io_uring Transport + +On kernels that expose `/sys/module/fuse/parameters/enable_uring = Y`, the +FUSE session attempts the FUSE-over-io_uring transport by default +(`AGENTFS_FUSE_URING`, bounded queue depth via `AGENTFS_FUSE_URING_DEPTH`) +and falls back to the classic `/dev/fuse` read/write loop when io_uring +setup is unavailable or fails. The transport is a performance detail only: +request semantics, reply contents, cache invalidation, and teardown bounds +MUST be identical on both legs, and unmount MUST join transport threads on +both legs without leaking the mount. + +### Overlay Base Reality + +Overlay mounts scope reads to the configured base directory and write only +to the delta database (see the sandbox invariants above). Two consequences +are contractual: + +1. Renaming a base-layer directory returns `EXDEV` rather than attempting a + recursive copy-up; callers (e.g. `mv`) then perform an explicit + copy+delete, which lands in the delta layer. +2. External mutation of the base tree under a live mount is detected where + it matters: partial-origin reads validate the recorded base fingerprint + and MUST fail rather than silently mix bytes from a drifted base file + (see Partial-Origin Overlay Mode). + ## Tool Calls The tool call tracking schema captures tool invocations for debugging, auditing, and analysis. @@ -519,13 +593,26 @@ Where `16877` = `0o040755` (directory with rwxr-xr-x permissions) ### Schema Migration -v0.5 is a layout-changing schema version. Databases created with v0.4 remain valid v0.4 databases until they are copied into a new v0.5 database: +Migrations are keyed by `PRAGMA user_version` and land any supported old +schema (v0.0, v0.2, v0.4) at the current version with one command: + +```bash +agentfs migrate +``` + +In-place migration requirements: + +1. Every migration step MUST be an additive, idempotent DDL change applied inside a single transaction that stamps `PRAGMA user_version` before committing. +2. Existing file contents MUST keep their recorded `chunk_size`; the in-place path does not re-chunk data. A defaulted `inline_threshold` MUST NOT exceed the database's recorded `chunk_size`. +3. Open paths (mount, fs, exec, SDK open) MUST NOT run version upgrades implicitly; they reject old schemas and direct the user to `agentfs migrate`. + +The copy-based mode rebuilds the database with the current chunk layout: ```bash -agentfs migrate-v0-5 --verify +agentfs migrate --copy --verify ``` -Migration requirements: +Copy-migration requirements: 1. The source database MUST NOT be modified in place. 2. The target database MUST be newly created unless an explicit overwrite option is used. @@ -536,8 +623,6 @@ Migration requirements: 7. Verification MUST run integrity checks and compare source/target metadata and file contents. 8. After checkpointing the target, copying only the main `.db` file MUST be sufficient to reopen and verify the target state. -The legacy `agentfs migrate` command is reserved for historical in-place upgrades through v0.4. It MUST NOT label a database as v0.5 without performing the copy-based v0.5 migration. - ### Consistency Rules 1. Root inode (ino=1) MUST always exist @@ -716,18 +801,24 @@ If a mapping exists, return `base_ino` instead of `delta_ino` in stat results. ### Partial-Origin Overlay Mode -Partial-origin copy-up is an experimental opt-in overlay mode enabled with -`AGENTFS_OVERLAY_PARTIAL_ORIGIN=1`. The default overlay behavior remains -whole-file copy-up. In opt-in mode, write-opening a regular base-layer file -creates a delta inode with the original size and metadata, records the base -path/fingerprint in `fs_partial_origin`, and stores only changed chunk indexes -in `fs_data` plus `fs_chunk_override`. Reads merge changed chunks from the -delta layer with unchanged chunks from the base layer. +Partial-origin copy-up is an opt-in overlay mode selected by the first-class +CLI policy `--partial-origin ` (with +`--partial-origin-threshold-bytes` sizing the `auto` cutoff). The default +overlay behavior remains whole-file copy-up (`off`). In opt-in mode, +write-opening a regular base-layer file creates a delta inode with the +original size and metadata, records the base path/fingerprint in +`fs_partial_origin`, and stores only changed chunk indexes in `fs_data` plus +`fs_chunk_override`. Reads merge changed chunks from the delta layer with +unchanged chunks from the base layer. The base fallback is part of the file's integrity contract. Implementations MUST fail reads of partial-origin files if the recorded base size or modification metadata no longer matches the current base file. Snapshot/restore of the main delta database is supported only when the same unchanged base path is available. +A database containing partial-origin rows is not portable on its own: +`agentfs backup` rejects it unless `--materialize` folds the base bytes in, +`agentfs materialize` produces a portable copy, and `agentfs integrity` +exposes the dependency via `--require-portable` and `--check-base`. #### Tables: `fs_partial_origin` and `fs_chunk_override` @@ -752,12 +843,11 @@ CREATE TABLE fs_chunk_override ( ) ``` -Phase 5.5 evidence keeps this mode opt-in: SDK coverage now includes remount, +Partial-origin stays opt-in. Coverage pinning the mode includes remount, main-DB snapshot restore, unlink cleanup/whiteout behavior, hardlink survival, -rename plus `readdir_plus`, truncate shrink/extend, base drift detection, and -large-edit smoke output that reports whether the env flag was enabled. It SHOULD -NOT be defaulted until the broader FUSE/CLI torture and POSIX gates pass with -the flag enabled. +rename plus `readdir_plus`, truncate shrink/extend, and base drift detection. +It SHOULD NOT become the default until the FUSE/CLI torture and POSIX gates +pass with the policy enabled. ### Consistency Rules @@ -855,6 +945,15 @@ Such extensions SHOULD use separate tables to maintain referential integrity. ## Revision History +### Version 0.5 + +- Default chunk size raised to 64 KiB for new filesystems (`chunk_size` in `fs_config`) +- Added inline storage for dense regular files at or below `inline_threshold` (default 4 KiB): `data_inline` and `storage_kind` columns on `fs_inode`, with layout rules and consistency checks +- Added `inline_threshold` to the required `fs_config` keys +- Added partial-origin overlay mode tables (`fs_partial_origin`, `fs_chunk_override`) behind the opt-in `--partial-origin` CLI policy +- Whiteout schema requires `parent_path`; legacy `fs_whiteout(path, created_at)` rows are synthesized on migration +- Schema migrations are keyed by `PRAGMA user_version`; `agentfs migrate` lands any supported old schema (v0.0, v0.2, v0.4) at the current version in place, and `--copy` rebuilds with the current chunk layout + ### Version 0.4 - Added nanosecond timestamp precision for `atime`, `mtime`, and `ctime` diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 00000000..67cf9d17 --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,246 @@ +# Testing AgentFS + +Linux is the first-tier platform: every gate below runs on Linux. macOS is +second-tier (NFS mount only) and is covered by the manual release gate at the +end of this document. + +## The honest gate: `scripts/gate.sh` + +`scripts/gate.sh` is the single developer and CI entrypoint. It fails on the +first failing command and runs, in order: + +1. `cargo +nightly fmt --all -- --check` +2. `cargo +nightly clippy --workspace --all-targets -- -D warnings` +3. `cargo +nightly test --workspace` +4. `cargo +nightly build --release --workspace --bins` +5. `crates/agentfs-cli/tests/all.sh` with `AGENTFS_GATE_STRICT=1` and + `AGENTFS_BIN` pointing at the release binary + (a SKIP is a failure on the designated runner) +6. `scripts/validation/phase8-validation.py --smoke` — the top-level Python + gate; it runs the noopen/flush/base-drift coherence harnesses internally +7. `scripts/validation/consistency-canon.sh` — the structural canon census + (crate DAG, sealed transport surfaces, file-size cap, tracing-only + logging, env-reads-at-the-config-edge, `await_holding_lock`, lock-order + headers, docs layout, changelog) + +Knobs: `AGENTFS_BIN` (defaults to `target/release/agentfs`), +`AGENTFS_GATE_SHELL_TIMEOUT` (default 900 s), `AGENTFS_GATE_PHASE8_TIMEOUT` +(default 20 s), `AGENTFS_GATE_ALLOWED_SKIPS` (forwarded to the shell suite, +see below), and the `CORRUPTION_TORTURE_*` variables forwarded to the +shell suite. The gate pins `TMPDIR` to a per-run scratch dir cleaned on exit +so dependency temp-file litter cannot accumulate on the host. + +CI (`.github/workflows/rust.yml`) runs the workspace job (fmt/clippy/build/test +on Linux and macOS, build+test on Linux arm64), the honest milestone gate +(`scripts/gate.sh` plus pjdfstest `phase5-ci`), and the release workflow. +The gate job first sets `kernel.apparmor_restrict_unprivileged_userns=0` so +the `agentfs run` suites exercise the sandbox instead of skipping on the +Ubuntu 24.04 runner image. FUSE-over-io_uring coverage stays local-only: the +CI kernel exposes `/sys/module/fuse/parameters/enable_uring` but ships it +disabled (`N`), so the kernel refuses ring registration, the panic-census +uring leg can never run there, and the gate job allowlists that one skip with +`AGENTFS_GATE_ALLOWED_SKIPS=fuse-sigint-panic-census`. The +`corruption-torture-uring` leg needs no allowlist entry: with `enable_uring=N` +the mount falls back to the legacy channel (the fallback is logged at INFO) +and the leg passes, exercising uring only on kernels that enable it. A +"starting fuse-over-io_uring queues" line in CI logs therefore does not mean +uring served requests; it precedes kernel acceptance of the ring +registration. The uring legs are honest only on a local machine whose kernel +sets `enable_uring=Y`. + +## Workspace tests and generated-docs parity + +```bash +cargo +nightly test --workspace +``` + +Two documentation files are generated from code and pinned by unit tests, so +doc drift fails the gate: + +- `docs/KNOBS.md` — regenerate with + `AGENTFS_UPDATE_KNOBS=1 cargo +nightly test -p agentfs-cli --lib knobs::tests::generated_knobs_doc_matches_declarations -- --exact` +- `docs/MANUAL.md` command reference — regenerate with + `AGENTFS_UPDATE_MANUAL=1 cargo +nightly test -p agentfs-cli --lib docs::tests::manual_help_parity -- --exact` + +## Shell integration suite + +```bash +AGENTFS_GATE_STRICT=1 crates/agentfs-cli/tests/all.sh +``` + +The suite covers init/mount/run/exec flows, syscall coverage, signal +teardown, corruption torture (both `AGENTFS_FUSE_URING=1` and `=0` legs), +sidecar cleanup, MCP server behavior, and a `cli-smoke` pass over the whole +user-level command surface (init, run, exec, clone, fs, timeline, +backup/materialize, integrity, migrate, MCP, ps, completions, and the +deprecated `nfs`/`mcp-server` aliases), and prints a PASS/SKIP/FAIL +summary. Every test runs out of its own `mktemp -d` root with trap cleanup +and honors `AGENTFS_BIN` (falling back to `cargo run`). In strict mode a SKIP +is red unless its test label is named in +`AGENTFS_GATE_ALLOWED_SKIPS=`, the escape hatch for runner +kernels that cannot provide a prerequisite at all; +`AGENTFS_GATE_FORCE_SKIP=` synthesizes a SKIP for testing the +runner itself. Never run the corruption +torture test concurrently with another mount, test suite, or benchmark. + +## Python validation gates + +All harnesses take `--agentfs-bin` (or `AGENTFS_BIN`); build a release binary +first for anything timing-sensitive. + +```bash +# Orchestrated Phase 8 policy gate (smoke profile is the milestone gate). +# Includes the noopen/flush/base-drift coherence harnesses as named gates. +python3 scripts/validation/phase8-validation.py --smoke --timeout 20 \ + --agentfs-bin "$PWD/target/release/agentfs" --output /tmp/vfs-val/phase8.json + +# Focused standalone runs of the coherence harnesses: +# Default-on FUSE semantics coherence (no-open and no-flush legs) +python3 scripts/validation/noopen-coherence.py --agentfs-bin "$PWD/target/release/agentfs" +python3 scripts/validation/flush-coherence.py --agentfs-bin "$PWD/target/release/agentfs" + +# Overlay base-drift rejection +python3 scripts/validation/external-base-mutation-coherence.py \ + --agentfs-bin "$PWD/target/release/agentfs" \ + --output /tmp/vfs-val/external-base-mutation.json +``` + +Shared harness helpers (binary resolution, subprocess handling with +process-group timeouts, JSON parsing) live in `scripts/validation/lib/`. +Historical one-off validators are archived under +`scripts/validation/archive/` (see its README) and are not part of any gate. + +Focused stress harnesses used by Phase 8 and available directly: +`phase8-concurrent-git-stress.py` (concurrent git correctness, base +immutability, portability), `fuse-serialization-stress.py` (read-lane +parallelism), `phase8-writeback-durability.py` (fsynced data survives +SIGKILL + remount), and `phase8-writeback-no-fsync-crash.py` (no-fsync crash +consistency: missing/prefix data allowed, corruption rejected). + +`AGENTFS_PROFILE=1` makes AgentFS emit `agentfs_profile_summary` counter +lines on exit; most harnesses parse and attach them to their JSON reports. + +## Benchmarks (local-only policy) + +The codex workload benchmark is a local performance gate, not a CI job. Run +it serialized on a quiet machine with a fresh release build, and compare +per-phase medians of 5 runs; single runs are noise: + +```bash +cargo +nightly build --release --workspace --bins +python3 scripts/validation/git-workload-benchmark-multi.py \ + --label bench --iterations 5 --warmup 1 \ + --agentfs-bin "$PWD/target/release/agentfs" \ + --source \ + --read-files 64 --read-bytes 4096 --edit-files 8 \ + --output /tmp/vfs-val/bench-multi.json --keep-iterations + +# Gate rule: red = >5% relative AND >10ms absolute per-phase median regression +python3 scripts/validation/bench-compare.py /tmp/vfs-val/bench-multi.json +``` + +Focused local benchmarks: `git-workload-benchmark.py` (single run with +`--profile` phase breakdown), `read-path-benchmark.py`, +`large-edit-benchmark.py` (one-byte edit to a large base file must grow the +delta DB by O(changed chunks), with `--partial-origin` / `--no-partial-origin` +legs), `base-read-benchmark.py`, and `agentfs-clone-benchmark.py`. + +## pjdfstest + +AgentFS keeps three pjdfstest modes: + +- `phase45-ci`: a conservative, unprivileged supported subset. +- `phase5-ci`: the expanded unprivileged supported subset (CI-wired in the + milestone gate). +- `full`: the upstream suite, used for exploratory POSIX triage. + +The supported subsets intentionally exclude root-only capabilities (`mknod` +for block/char devices, successful `chown`/`lchown`, alternate uid/gid +execution); exclusions are tracked in +`scripts/validation/posix/pjdfstest/known-gaps.tsv`. + +Install pjdfstest locally: + +```bash +git clone https://github.com/pjd/pjdfstest.git +cd pjdfstest +autoreconf -ifs +./configure --prefix="$HOME/.local" +make pjdfstest +install -m 0755 pjdfstest "$HOME/.local/bin/pjdfstest" +``` + +Run the supported gate against a workspace build: + +```bash +cargo +nightly build --workspace +scripts/validation/posix/run-pjdfstest.sh \ + --agentfs-bin "$PWD/target/debug/agentfs" \ + --pjdfstest-dir /path/to/pjdfstest \ + --profile phase5-ci +``` + +The harness writes a report directory (TAP log, exit status, selected +profile/manifest/tests, known-gap taxonomy) and exits `77` when +prerequisites are missing. `--list-profiles` lists profiles; +`--partial-origin` mounts the fixture with the partial-origin policy enabled. +Summarize a log with +`scripts/validation/posix/summarize-pjdfstest-log.py +--known-gaps scripts/validation/posix/pjdfstest/known-gaps.tsv`. +Do not treat `full` as a required gate while known gaps remain. + +## Production safety checks + +```bash +# SQLite + AgentFS schema invariants (exit nonzero on any failed check) +agentfs integrity .agentfs/my-agent.db --json + +# Portable snapshot: checkpoint WAL, copy main DB, reopen, re-verify +agentfs backup .agentfs/my-agent.db /tmp/my-agent-backup.db --verify +``` + +Partial-origin overlay databases are rejected by plain `backup` because +their contents depend on an external base tree; use `backup --materialize` +or `agentfs materialize` first, and audit the dependency with +`agentfs integrity --require-portable --check-base`. + +## macOS: second-tier platform and the manual release gate + +macOS support is explicitly second-tier: mounting uses the NFS backend only +(no FUSE, no `agentfs ps`), and NFS protocol semantics are validated by cargo +protocol/unit tests on Linux. There is no macOS coverage in the automated +gate, so the macOS NFS git validation script is a **manual release gate**: it +must be run on real macOS hardware before a release is cut. + +```bash +cargo +nightly build --release --workspace --bins +scripts/validation/macos-nfs-git-validation.sh \ + --agentfs-bin "$PWD/target/release/agentfs" +``` + +The harness is temp-directory scoped, initializes a fresh AgentFS database, +mounts it with `agentfs mount --backend nfs`, then runs `git init`, +`git add`, `git commit`, and `git fsck --strict`, and verifies at least one +loose object was written. It then verifies the `agentfs run` Seatbelt +read-scoping posture: a secret file in an unallowed directory under `$HOME` +must be unreadable from inside the sandbox (permission error, no content +leak), and re-running with `--allow ` must make it readable. The +generated profile itself is pinned by macOS-gated unit tests +(`cmd::run::tests::darwin_read_scoping`) that run in the CI macos-latest +workspace job; this script is the runtime check. A passing run ends with +`macOS NFS git + run read-scoping validation passed`. Unsupported platforms +or missing prerequisites exit `77`; on Linux that skip is expected, not a +failure. A release SHOULD NOT ship without a passing run of this script on +real hardware. + +Beyond the script, the manual hardware run must also confirm three behaviors +the Linux toolchain cannot exercise: dynamic profile paths now travel as +Seatbelt `(param "NAME")` references with `-D NAME=value` definitions on the +`/usr/bin/sandbox-exec` command line (spot-check that a session under a +directory with spaces or quotes in its name still mounts and runs); +`/System/Volumes/Preboot` has a metadata literal so path resolution down to +the dyld cryptex root (`/System/Volumes/Preboot/Cryptexes`) can stat every +component (spot-check that dynamically linked binaries start under the +sandbox); and `agentfs run ` must exit `127` (`126` for a +present but non-executable file), matching `agentfs exec` and the Linux run +path. diff --git a/examples/ai-sdk-just-bash/.gitignore b/examples/ai-sdk-just-bash/.gitignore deleted file mode 100644 index e48da4c7..00000000 --- a/examples/ai-sdk-just-bash/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -dist/ -.agentfs/ diff --git a/examples/ai-sdk-just-bash/README.md b/examples/ai-sdk-just-bash/README.md deleted file mode 100644 index b4e1d77f..00000000 --- a/examples/ai-sdk-just-bash/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# AI SDK + just-bash Code Explorer Agent - -An interactive AI agent that combines [Vercel AI SDK](https://sdk.vercel.ai/), [just-bash](https://github.com/vercel-labs/just-bash) for bash command execution, and [AgentFS](https://github.com/tursodatabase/agentfs) for persistent filesystem storage. - -This example is forked from the [just-bash bash-agent example](https://github.com/vercel-labs/just-bash/tree/main/examples/bash-agent) with AgentFS integration added. - -## How It Works - -- **Vercel AI SDK** - Orchestrates the AI agent with Claude as the model -- **just-bash** - Provides a bash tool that the AI can use to execute shell commands -- **AgentFS** - Backs the virtual filesystem with SQLite for persistence across sessions - -## Files - -- `main.ts` - Entry point -- `agent.ts` - Agent logic using AI SDK's `streamText` with just-bash's `createBashTool` -- `shell.ts` - Interactive readline shell - -## Setup - -1. Install dependencies: - - ```bash - npm install - ``` - -2. Set your Anthropic API key: - - ```bash - export ANTHROPIC_API_KEY=your-key-here - ``` - -3. Run: - ```bash - npm start - ``` - -## Usage - -Ask questions like: - -- "What commands are available?" -- "How is the grep command implemented?" -- "Show me the Bash class" -- "Find all test files" - -Type `exit` to quit. - -## Development - -```bash -npm run typecheck -``` diff --git a/examples/ai-sdk-just-bash/agent.ts b/examples/ai-sdk-just-bash/agent.ts deleted file mode 100644 index 38dd6457..00000000 --- a/examples/ai-sdk-just-bash/agent.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * AI agent with persistent AgentFS storage - * - * This file contains only the agent logic - see shell.ts for the interactive loop. - * Uses AgentFs to provide persistent filesystem storage backed by SQLite. - * - * The agent starts with the agentfs source code pre-loaded, so you can - * explore the agentfs codebase using agentfs itself! - */ - -import * as nodeFs from "node:fs"; -import * as path from "node:path"; -import { glob } from "glob"; -import { anthropic } from "@ai-sdk/anthropic"; -import { streamText, stepCountIs } from "ai"; -import { createBashTool } from "bash-tool"; -import { Bash } from "just-bash"; -import { agentfs } from "agentfs-sdk/just-bash"; - -export interface AgentRunner { - chat( - message: string, - callbacks: { - onText: (text: string) => void; - } - ): Promise; -} - -export interface CreateAgentOptions { - onToolCall?: (command: string) => void; - onText?: (text: string) => void; -} - -/** - * Creates an agent runner with persistent filesystem storage - * - * Uses AgentFs backed by SQLite - files persist across sessions. - */ -export async function createAgent( - options: CreateAgentOptions = {} -): Promise { - // Open AgentFS for persistent storage - const fs = await agentfs({ id: "just-bash-agent" }); - - // Seed agentfs source files on first run - const agentfsRoot = path.resolve(import.meta.dirname, "../.."); - if (!(await fs.exists("/README.md"))) { - console.log("Seeding AgentFS with agentfs source files..."); - - // Find all source files - const patterns = [ - "**/*.ts", - "**/*.rs", - "**/*.toml", - "**/*.json", - "**/*.md", - ]; - const ignorePatterns = [ - "**/node_modules/**", - "**/dist/**", - "**/target/**", - "**/.git/**", - "**/examples/just_bash/**", - ]; - - // Collect all files first - const allFiles: string[] = []; - for (const pattern of patterns) { - const files = await glob(pattern, { - cwd: agentfsRoot, - ignore: ignorePatterns, - nodir: true, - }); - allFiles.push(...files); - } - - // Copy files with progress - let count = 0; - const total = allFiles.length; - for (const file of allFiles) { - const srcPath = path.join(agentfsRoot, file); - const destPath = "/" + file; - - // Create parent directories - const dir = path.dirname(destPath); - if (dir !== "/") { - await fs.mkdir(dir, { recursive: true }).catch(() => {}); - } - - // Copy file - const content = nodeFs.readFileSync(srcPath, "utf-8"); - await fs.writeFile(destPath, content); - - count++; - if (count % 10 === 0 || count === total) { - process.stdout.write(`\rSeeding: ${count}/${total} files...`); - } - } - console.log("\nSeeded AgentFS with agentfs source files."); - } - - // Create a just-bash Bash instance with AgentFS filesystem - const bash = new Bash({ fs }); - - // Create the bash toolkit with the just-bash sandbox - const bashToolkit = await createBashTool({ - sandbox: bash, - extraInstructions: `You are exploring the AgentFS codebase - a persistent filesystem for AI agents. -The filesystem is backed by AgentFS itself (SQLite), so files persist across sessions. - -Use bash commands to explore: -- ls to see the project structure -- cat README.md to read documentation -- grep -r "pattern" . to search code -- find . -name "*.ts" to find TypeScript files - -Key directories: -- /sdk/typescript/src - TypeScript SDK source -- /cli/src - CLI source (Rust) -- /integrations - Framework integrations`, - onBeforeBashCall: options.onToolCall - ? ({ command }) => { - options.onToolCall!(command); - return { command }; - } - : undefined, - }); - - const history: Array<{ role: "user" | "assistant"; content: string }> = []; - - return { - async chat(message, callbacks) { - history.push({ role: "user", content: message }); - - let fullText = ""; - - const result = streamText({ - model: anthropic("claude-sonnet-4-20250514"), - tools: bashToolkit.tools, - stopWhen: stepCountIs(50), - messages: history, - }); - - for await (const chunk of result.textStream) { - options.onText?.(chunk); - callbacks.onText(chunk); - fullText += chunk; - } - - history.push({ role: "assistant", content: fullText }); - }, - }; -} diff --git a/examples/ai-sdk-just-bash/main.ts b/examples/ai-sdk-just-bash/main.ts deleted file mode 100644 index d06bc68e..00000000 --- a/examples/ai-sdk-just-bash/main.ts +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env npx tsx -/** - * just-bash Agent with AgentFS - * - * Usage: npx tsx main.ts - * - * Requires ANTHROPIC_API_KEY environment variable. - */ - -import { createAgent } from "./agent.js"; -import { runShell } from "./shell.js"; - -let lastWasToolCall = false; - -const agent = await createAgent({ - onToolCall: (command) => { - const prefix = lastWasToolCall ? "" : "\n"; - console.log( - `${prefix}\x1b[34m\x1b[1mExecuting bash tool:\x1b[0m \x1b[36m${command.trim()}\x1b[0m` - ); - lastWasToolCall = true; - }, - onText: () => { - lastWasToolCall = false; - }, -}); -runShell(agent); diff --git a/examples/ai-sdk-just-bash/package-lock.json b/examples/ai-sdk-just-bash/package-lock.json deleted file mode 100644 index f44805b4..00000000 --- a/examples/ai-sdk-just-bash/package-lock.json +++ /dev/null @@ -1,1137 +0,0 @@ -{ - "name": "just-bash-agentfs-example", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "just-bash-agentfs-example", - "version": "1.0.0", - "dependencies": { - "@ai-sdk/anthropic": "^3.0.0", - "agentfs-sdk": "file:../../sdk/typescript", - "ai": "^6.0.0", - "bash-tool": "^1.0.0", - "glob": "^11.0.0", - "just-bash": "^2.0.0" - }, - "devDependencies": { - "@types/node": "^22.0.0", - "typescript": "^5.0.0" - } - }, - "../../sdk/typescript": { - "name": "agentfs-sdk", - "version": "0.5.0-pre.1", - "license": "MIT", - "dependencies": { - "@tursodatabase/database": "^0.4.0-pre.18", - "@tursodatabase/database-common": "^0.4.0-pre.18", - "buffer": "^6.0.3" - }, - "devDependencies": { - "@tursodatabase/database-wasm": "^0.4.0-pre.18", - "@types/node": "^20.0.0", - "@vitest/browser": "^4.0.16", - "@vitest/browser-playwright": "^4.0.16", - "@vitest/ui": "^4.0.1", - "just-bash": "^2.0.0", - "typescript": "^5.3.0", - "vitest": "^4.0.1" - }, - "peerDependencies": { - "just-bash": ">=2.0.0" - }, - "peerDependenciesMeta": { - "just-bash": { - "optional": true - } - } - }, - "node_modules/@ai-sdk/anthropic": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.8.tgz", - "integrity": "sha512-x5G07iwQB/Sh+1dFLWyZgdOVRnh9X/o8YaPCr+Ju3ie8Amp1mvHLz+c2i3UKe6/8leIwr/TXjrwyJRJk9mkq2g==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.2", - "@ai-sdk/provider-utils": "4.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/gateway": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.9.tgz", - "integrity": "sha512-EA5dZIukimwoJ9HIPuuREotAqaTItpdc/yImzVF0XGNg7B0YRJmYI8Uq3aCMr87vjr1YB1cWUfnrTt6OJ9eHiQ==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.2", - "@ai-sdk/provider-utils": "4.0.4", - "@vercel/oidc": "3.1.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/provider": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.2.tgz", - "integrity": "sha512-HrEmNt/BH/hkQ7zpi2o6N3k1ZR1QTb7z85WYhYygiTxOQuaml4CMtHCWRbric5WPU+RNsYI7r1EpyVQMKO1pYw==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/provider-utils": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.4.tgz", - "integrity": "sha512-VxhX0B/dWGbpNHxrKCWUAJKXIXV015J4e7qYjdIU9lLWeptk0KMLGcqkB4wFxff5Njqur8dt8wRi1MN9lZtDqg==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.2", - "@standard-schema/spec": "^1.1.0", - "eventsource-parser": "^3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@borewit/text-codec": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", - "integrity": "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@mixmark-io/domino": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", - "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==", - "license": "BSD-2-Clause" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", - "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@vercel/oidc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz", - "integrity": "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==", - "license": "Apache-2.0", - "engines": { - "node": ">= 20" - } - }, - "node_modules/agentfs-sdk": { - "resolved": "../../sdk/typescript", - "link": true - }, - "node_modules/ai": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.16.tgz", - "integrity": "sha512-pJDNvgKZJ/rFH3UtNOatJQnR7ajo8I5tSKWpObKmM6TlYADdKL90ustJf36YoaOGSYdP+Wg5GsNL9KJJ5Lm2ng==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/gateway": "3.0.9", - "@ai-sdk/provider": "3.0.2", - "@ai-sdk/provider-utils": "4.0.4", - "@opentelemetry/api": "1.9.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/bash-tool": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bash-tool/-/bash-tool-1.0.0.tgz", - "integrity": "sha512-GO8P1dVZnBPqZvjv5lXFtdZGDR9BM0A4UmE3NCfbUhr7nVv2K0qwDN/tPokaG839UcsZocGeU+Z/ELmFpqxRYw==", - "license": "MIT", - "dependencies": { - "fast-glob": "^3.3.2", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@vercel/sandbox": "*", - "ai": "^6.0.0", - "just-bash": "*" - }, - "peerDependenciesMeta": { - "@vercel/sandbox": { - "optional": true - }, - "just-bash": { - "optional": true - } - } - }, - "node_modules/bash-tool/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/diff": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", - "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-type": { - "version": "21.3.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.0.tgz", - "integrity": "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==", - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/just-bash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/just-bash/-/just-bash-2.0.0.tgz", - "integrity": "sha512-dIxmIwoHMZysQfjOm5pLlpE75gb0QNMTzuhUSaip/YbSSM4x8jA77EdVEex2TH7pIE5PocApEv3WxavsnVQLEA==", - "license": "Apache-2.0", - "dependencies": { - "diff": "^8.0.2", - "file-type": "^21.2.0", - "minimatch": "^10.1.1", - "sprintf-js": "^1.1.3", - "turndown": "^7.2.2" - }, - "bin": { - "just-bash": "dist/bin/just-bash.js", - "just-bash-shell": "dist/bin/shell/shell.js" - } - }, - "node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", - "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "license": "BSD-3-Clause" - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strtok3": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.2.1", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/turndown": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.2.tgz", - "integrity": "sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==", - "license": "MIT", - "dependencies": { - "@mixmark-io/domino": "^2.2.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/zod": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", - "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/examples/ai-sdk-just-bash/package.json b/examples/ai-sdk-just-bash/package.json deleted file mode 100644 index d7830ba6..00000000 --- a/examples/ai-sdk-just-bash/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "just-bash-agentfs-example", - "version": "1.0.0", - "description": "Interactive AI agent with persistent AgentFS storage", - "type": "module", - "scripts": { - "start": "npx tsx main.ts", - "typecheck": "npx tsc --noEmit" - }, - "dependencies": { - "@ai-sdk/anthropic": "^3.0.0", - "agentfs-sdk": "file:../../sdk/typescript", - "ai": "^6.0.0", - "bash-tool": "^1.0.0", - "glob": "^11.0.0", - "just-bash": "^2.0.0" - }, - "devDependencies": { - "@types/node": "^22.0.0", - "typescript": "^5.0.0" - } -} diff --git a/examples/ai-sdk-just-bash/shell.ts b/examples/ai-sdk-just-bash/shell.ts deleted file mode 100644 index d33f8b35..00000000 --- a/examples/ai-sdk-just-bash/shell.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Interactive shell for the just-bash agent with AgentFS - */ - -import * as readline from "node:readline"; -import type { AgentRunner } from "./agent.js"; - -const colors = { - reset: "\x1b[0m", - bold: "\x1b[1m", - dim: "\x1b[2m", - cyan: "\x1b[36m", - green: "\x1b[32m", - yellow: "\x1b[33m", - blue: "\x1b[34m", -}; - -export function runShell(agent: AgentRunner): void { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - console.log(`${colors.cyan}${colors.bold}╔══════════════════════════════════════════════════════════════╗ -║ just-bash Agent with AgentFS ║ -║ Persistent filesystem backed by SQLite ║ -╚══════════════════════════════════════════════════════════════╝${colors.reset} -`); - console.log( - `${colors.dim}Type your question and press Enter. Type 'exit' to quit.${colors.reset}\n` - ); - - const prompt = (): void => { - rl.question(`${colors.green}You:${colors.reset} `, async (input: string) => { - const trimmed = input.trim(); - - if (!trimmed) { - prompt(); - return; - } - - if (trimmed.toLowerCase() === "exit") { - console.log("\nGoodbye!"); - rl.close(); - process.exit(0); - } - - process.stdout.write( - `\n${colors.blue}${colors.bold}Agent:${colors.reset} ` - ); - - try { - await agent.chat(trimmed, { - onText: (text) => process.stdout.write(text), - }); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error(`\n${colors.yellow}Error: ${message}${colors.reset}`); - } - - console.log(""); - prompt(); - }); - }; - - prompt(); -} diff --git a/examples/ai-sdk-just-bash/tsconfig.json b/examples/ai-sdk-just-bash/tsconfig.json deleted file mode 100644 index 55986f37..00000000 --- a/examples/ai-sdk-just-bash/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "noEmit": true - }, - "include": ["*.ts", "../../src/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/examples/claude-agent/research-assistant/.env.example b/examples/claude-agent/research-assistant/.env.example deleted file mode 100644 index 9328d82f..00000000 --- a/examples/claude-agent/research-assistant/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -# Anthropic API Key (required) -ANTHROPIC_API_KEY=your-api-key-here - -# Optional: Custom AgentFS agent ID -# AGENTFS_ID=research-assistant diff --git a/examples/claude-agent/research-assistant/.gitignore b/examples/claude-agent/research-assistant/.gitignore deleted file mode 100644 index 1d90beee..00000000 --- a/examples/claude-agent/research-assistant/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -# Dependencies -node_modules/ - -# Build output -dist/ - -# Environment files -.env -.env.local - -# AgentFS local databases -.agentfs/ - -# IDE -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# OS -.DS_Store -Thumbs.db - -# Logs -*.log -npm-debug.log* diff --git a/examples/claude-agent/research-assistant/README.md b/examples/claude-agent/research-assistant/README.md deleted file mode 100644 index c55ab403..00000000 --- a/examples/claude-agent/research-assistant/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Research Assistant (VLDB + SIGMOD) - -A minimal research assistant that answers database research questions by finding most relevant papers from the VLDB and SIGMOD conference proceedings. - -## Getting Started - -Install dependencies: - -```console -npm install -``` - -Ask a research question: - -``` -npm run ask -- "How do learned indexes compare to B+ trees?" -``` diff --git a/examples/claude-agent/research-assistant/package-lock.json b/examples/claude-agent/research-assistant/package-lock.json deleted file mode 100644 index 7132262b..00000000 --- a/examples/claude-agent/research-assistant/package-lock.json +++ /dev/null @@ -1,905 +0,0 @@ -{ - "name": "research-assistant-claude-agent", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "research-assistant-claude-agent", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.1.0", - "agentfs-sdk": "file:../../../sdk/typescript", - "dotenv": "^16.4.7", - "pdf-parse": "^1.1.1", - "zod": "^3.24.1" - }, - "bin": { - "research-assistant": "dist/cli.js" - }, - "devDependencies": { - "@types/node": "^24.10.0", - "@types/pdf-parse": "^1.1.4", - "tsx": "^4.19.1", - "typescript": "^5.9.3" - }, - "engines": { - "node": ">=20.9.0" - } - }, - "../../../sdk/typescript": { - "name": "agentfs-sdk", - "version": "0.1.0", - "license": "MIT", - "dependencies": { - "@tursodatabase/database": "^0.3.2" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "@vitest/ui": "^4.0.1", - "typescript": "^5.3.0", - "vitest": "^4.0.1" - } - }, - "node_modules/@anthropic-ai/claude-agent-sdk": { - "version": "0.1.37", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.1.37.tgz", - "integrity": "sha512-LMfqMIPLTz0vRhpcO7hpPJ5L6Bg24y5/PaqZvwAUNZ/GR3OAl7xmJR7IryIR6m8Pyd/6Hs2yBU8j86Os+wHFQQ==", - "license": "SEE LICENSE IN README.md", - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "^0.33.5", - "@img/sharp-darwin-x64": "^0.33.5", - "@img/sharp-linux-arm": "^0.33.5", - "@img/sharp-linux-arm64": "^0.33.5", - "@img/sharp-linux-x64": "^0.33.5", - "@img/sharp-win32-x64": "^0.33.5" - }, - "peerDependencies": { - "zod": "^3.24.1" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/pdf-parse": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@types/pdf-parse/-/pdf-parse-1.1.5.tgz", - "integrity": "sha512-kBfrSXsloMnUJOKi25s3+hRmkycHfLK6A09eRGqF/N8BkQoPUmaCr+q8Cli5FnfohEz/rsv82zAiPz/LXtOGhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/agentfs-sdk": { - "resolved": "../../../sdk/typescript", - "link": true - }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/node-ensure": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", - "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==", - "license": "MIT" - }, - "node_modules/pdf-parse": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.4.tgz", - "integrity": "sha512-XRIRcLgk6ZnUbsHsYXExMw+krrPE81hJ6FQPLdBNhhBefqIQKXu/WeTgNBGSwPrfU0v+UCEwn7AoAUOsVKHFvQ==", - "license": "MIT", - "dependencies": { - "node-ensure": "^0.0.0" - }, - "engines": { - "node": ">=6.8.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/mehmet-kozan" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/examples/claude-agent/research-assistant/package.json b/examples/claude-agent/research-assistant/package.json deleted file mode 100644 index 4cfd37ce..00000000 --- a/examples/claude-agent/research-assistant/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "research-assistant-claude-agent", - "version": "1.0.0", - "description": "A research assistant built with Claude Agent SDK that answers database research questions", - "type": "module", - "main": "dist/cli.js", - "bin": { - "research-assistant": "dist/cli.js" - }, - "scripts": { - "build": "tsc", - "ask": "tsx src/cli.ts", - "dev": "tsx src/cli.ts" - }, - "keywords": [ - "claude", - "agent", - "research", - "database", - "ai" - ], - "author": "", - "license": "ISC", - "engines": { - "node": ">=20.9.0" - }, - "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.1.0", - "agentfs-sdk": "file:../../../sdk/typescript", - "dotenv": "^16.4.7", - "pdf-parse": "^1.1.1", - "zod": "^3.24.1" - }, - "devDependencies": { - "@types/node": "^24.10.0", - "@types/pdf-parse": "^1.1.4", - "tsx": "^4.19.1", - "typescript": "^5.9.3" - } -} diff --git a/examples/claude-agent/research-assistant/src/agent.ts b/examples/claude-agent/research-assistant/src/agent.ts deleted file mode 100644 index f9920586..00000000 --- a/examples/claude-agent/research-assistant/src/agent.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { query, createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk'; -import { semanticScholarTool } from './tools/semantic-tool.js'; -import { pdfFetchTool, resolvePdfUrlTool } from './tools/pdf-tool.js'; -import { getAgentFS } from './utils/agentfs.js'; - -const AGENT_INSTRUCTIONS = ` -You are a database research expert who provides comprehensive, evidence-based analysis. - -When a user asks a research question: - -1. **Search for papers**: Use the 'search-semanticscholar-vldb-sigmod' tool with the user's query to find relevant papers from VLDB and SIGMOD venues. - -2. **Get full paper content**: For the most relevant papers (typically 3-5 papers): - - If a paper has a pdfUrl, use the 'fetch-pdf-text' tool to get the complete paper text - - If a paper only has a regular URL, first try 'resolve-pdf-url' to find the PDF link, then fetch it - - Read the FULL TEXT carefully to extract specific findings, numbers, and comparisons - - If PDF fetching fails, work with the abstract, but note the limitation - -3. **Analyze the content**: Review the full paper texts, focusing on: - - Key findings and performance metrics (extract specific numbers!) - - Comparisons and benchmarks between systems - - Trade-offs and limitations discussed by authors - - Theoretical and practical insights - - Methodologies and experimental setups - -4. **Synthesize a comprehensive answer** that: - - Directly answers the research question with specific evidence from the papers - - Includes concrete performance numbers, comparisons, and trade-offs - - Cites sources using the format (FirstAuthor, Year) - e.g., (Marcus, 2020), (Liu, 2024) - - Uses semicolons for multiple citations: (Author1, Year1; Author2, Year2) - - Highlights areas of agreement or disagreement between papers - - Organizes findings into clear sections with headers like "Key Findings", "Performance Metrics", "Trade-offs" - - Mentions when papers show different results or conflicting evidence - -5. **Include a references section** at the end with full citations in this format: - - AuthorLastName et al. (Year). Paper Title — Venue Year - - URL - -CRITICAL RULES: -- DO NOT just list papers. ANSWER the question with evidence! -- DO NOT give superficial summaries. Include SPECIFIC DETAILS from the papers! -- ALWAYS fetch and read full PDFs for thorough analysis - abstracts alone are insufficient! -- Include ACTUAL NUMBERS and metrics from the papers, not vague descriptions! -- Be thorough, accurate, and evidence-based in your analysis! -`; - -// Create an MCP server with our custom tools -const toolServer = createSdkMcpServer({ - name: 'research-tools', - version: '1.0.0', - tools: [semanticScholarTool, pdfFetchTool, resolvePdfUrlTool], -}); - -export async function runResearchAgent(question: string): Promise { - // Initialize AgentFS - await getAgentFS(); - - let fullResponse = ''; - - // Run the agent with the Claude Agent SDK query function - const result = query({ - prompt: question, - options: { - model: 'sonnet', - systemPrompt: AGENT_INSTRUCTIONS, - mcpServers: { - 'research-tools': toolServer, - }, - maxTurns: 50, - permissionMode: 'bypassPermissions', - }, - }); - - // Stream messages and collect the final response - for await (const message of result) { - if (message.type === 'assistant') { - // Collect text from assistant messages - for (const content of message.message.content) { - if (content.type === 'text') { - fullResponse += content.text; - } - } - } else if (message.type === 'result') { - // Final result message - break; - } - } - - return fullResponse; -} diff --git a/examples/claude-agent/research-assistant/src/cli.ts b/examples/claude-agent/research-assistant/src/cli.ts deleted file mode 100644 index 9fa78b91..00000000 --- a/examples/claude-agent/research-assistant/src/cli.ts +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env node -import 'dotenv/config'; -import { runResearchAgent } from './agent.js'; - -async function main() { - const args = process.argv.slice(2); - const question = args.join(' ').trim(); - - if (!question) { - console.error('Usage: npm run ask -- ""'); - process.exit(1); - } - - try { - console.log('Researching your question...\n'); - - const response = await runResearchAgent(question); - - console.log(response); - } catch (err: any) { - console.error('Error:', err?.message || err); - process.exit(1); - } -} - -main(); diff --git a/examples/claude-agent/research-assistant/src/tools/pdf-tool.ts b/examples/claude-agent/research-assistant/src/tools/pdf-tool.ts deleted file mode 100644 index af3f7355..00000000 --- a/examples/claude-agent/research-assistant/src/tools/pdf-tool.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { tool } from '@anthropic-ai/claude-agent-sdk'; -import { z } from 'zod'; -import { fetchPdfTextCached, resolvePdfUrl } from '../utils/pdf.js'; -import { getAgentFS } from '../utils/agentfs.js'; - -export const pdfFetchTool = tool( - 'fetch-pdf-text', - 'Fetches and parses full text content from a research paper PDF URL. Use this when you need the complete paper content for detailed analysis beyond the abstract.', - { - pdfUrl: z.string().url().describe('Direct URL to the PDF file'), - paperTitle: z.string().describe('Title of the paper (used for caching)'), - year: z.number().describe('Publication year (used for caching)'), - }, - async ({ pdfUrl, paperTitle, year }) => { - // Generate cache key from title and year - const cacheKey = `${paperTitle}-${year}` - .toLowerCase() - .replace(/[^a-z0-9-_]+/g, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, '') - .slice(0, 150); - - const agentfs = await getAgentFS(); - - // Check if already cached - const txtPath = `/papers/${cacheKey}.txt`; - try { - const cached = await agentfs.fs.readFile(txtPath); - if (cached && cached.length > 300) { - return { - content: [{ - type: 'text' as const, - text: JSON.stringify({ text: cached, cached: true }, null, 2), - }], - }; - } - } catch { - // Not cached, proceed to fetch - } - - // Fetch and parse PDF - const text = await fetchPdfTextCached(cacheKey, pdfUrl); - - if (!text || text.length < 300) { - throw new Error('PDF text extraction failed or returned insufficient content'); - } - - return { - content: [{ - type: 'text' as const, - text: JSON.stringify({ text, cached: false }, null, 2), - }], - }; - } -); - -export const resolvePdfUrlTool = tool( - 'resolve-pdf-url', - 'Attempts to resolve a landing page URL (like a DOI or ACM DL page) to a direct PDF URL. Use this when you have a paper URL but need the PDF link.', - { - url: z.string().url().describe('Landing page URL for the paper'), - }, - async ({ url }) => { - const pdfUrl = await resolvePdfUrl(url); - - return { - content: [{ - type: 'text' as const, - text: JSON.stringify({ - pdfUrl: pdfUrl || undefined, - success: !!pdfUrl, - }, null, 2), - }], - }; - } -); diff --git a/examples/claude-agent/research-assistant/src/tools/semantic-tool.ts b/examples/claude-agent/research-assistant/src/tools/semantic-tool.ts deleted file mode 100644 index 2cb6dda2..00000000 --- a/examples/claude-agent/research-assistant/src/tools/semantic-tool.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { tool } from '@anthropic-ai/claude-agent-sdk'; -import { z } from 'zod'; - -export const paperSchema = z.object({ - paperId: z.string(), - title: z.string(), - authors: z.array(z.string()), - venue: z.string(), - year: z.number(), - url: z.string().url(), - abstractText: z.string().optional(), - pdfUrl: z.string().url().optional(), -}); -export type Paper = z.infer; - -type S2Author = { name?: string }; -type S2Venue = { displayName?: string } | null; -type S2ExternalIds = { DOI?: string | null }; -type S2Paper = { - paperId?: string; - title?: string; - year?: number; - authors?: S2Author[]; - url?: string | null; - abstract?: string | null; - venue?: string | null; - publicationVenue?: S2Venue; - externalIds?: S2ExternalIds; - isOpenAccess?: boolean; - openAccessPdf?: { url?: string | null } | null; -}; - -type S2SearchResponse = { - data?: S2Paper[]; - total?: number; -}; - -function isTargetVenue(venue?: string | null): boolean { - if (!venue) return false; - const v = venue.toLowerCase(); - return ( - v.includes('sigmod') || - v.includes('acm sigmod') || - v.includes('sigmod conference') || - v.includes('proceedings of the vldb endowment') || - v.includes('pvl db') || - v.includes('pvlb') || - v.includes('pvlbd') || - v.includes('vldb') // keep broad to catch PVLDB; risk: VLDB Journal may appear - ); -} - -async function sleep(ms: number) { - return new Promise((r) => setTimeout(r, ms)); -} - -async function fetchWithBackoff(url: string, init: RequestInit, maxRetries = 5, initialDelayMs = 500): Promise { - let attempt = 0; - let delay = initialDelayMs; - while (true) { - const res = await fetch(url, init); - if (res.status !== 429 && res.status < 500) { - return res; - } - if (attempt >= maxRetries) { - return res; - } - // Prefer Retry-After header if present - const retryAfter = res.headers.get('Retry-After'); - let waitMs = delay; - if (retryAfter) { - const secs = Number(retryAfter); - if (!Number.isNaN(secs) && secs > 0) waitMs = secs * 1000; - } - await sleep(waitMs); - attempt += 1; - delay = Math.min(delay * 2, 8000); - } -} - -export async function searchSemanticScholar(query: string): Promise { - const base = 'https://api.semanticscholar.org/graph/v1/paper/search'; - const params = new URLSearchParams({ - query, - limit: '100', - fields: 'title,venue,publicationVenue,year,authors,url,abstract,externalIds,isOpenAccess,openAccessPdf', - }); - const url = `${base}?${params.toString()}`; - const res = await fetchWithBackoff( - url, - { - headers: { - Accept: 'application/json', - 'User-Agent': 'research-assistant', - }, - }, - 5, - 500, - ); - if (!res.ok) { - throw new Error(`Semantic Scholar request failed with status ${res.status}`); - } - const data = (await res.json()) as S2SearchResponse; - const arr = data.data || []; - const mapped: Paper[] = arr - .map((p) => { - const title = (p.title || '').trim(); - const authors = (p.authors || []).map((a) => (a.name || '').trim()).filter(Boolean); - const venue = - (p.publicationVenue?.displayName || p.venue || '').toString().trim(); - const year = Number(p.year || 0); - const doi = p.externalIds?.DOI || null; - const rawUrl = (doi ? `https://doi.org/${doi}` : p.url || '').toString().trim(); - const url = rawUrl; - const paperId = (doi || p.paperId || url || title).toString(); - const abstractText = (p.abstract || undefined)?.toString(); - let pdfUrl = - (p.openAccessPdf?.url || undefined) || - (doi && doi.toLowerCase().startsWith('10.48550/arxiv.') - ? `https://arxiv.org/pdf/${doi.toLowerCase().split('arxiv.')[1]}.pdf` - : undefined); - // Fallback: if the URL is an arXiv abstract, derive the PDF URL - try { - const u = new URL(url); - if (u.host.includes('arxiv.org')) { - const absMatch = u.pathname.match(/^\/abs\/(.+)$/i); - if (absMatch && absMatch[1]) { - pdfUrl = `https://arxiv.org/pdf/${absMatch[1]}.pdf`; - } else { - const pdfMatch = u.pathname.match(/^\/pdf\/(.+)$/i); - if (pdfMatch && pdfMatch[1] && !pdfMatch[1].endsWith('.pdf')) { - pdfUrl = `https://arxiv.org/pdf/${pdfMatch[1]}.pdf`; - } - } - } - } catch { - // ignore URL parse errors - } - return { - paperId, - title, - authors, - venue, - year, - url, - abstractText, - pdfUrl, - } as Paper; - }) - .filter((p) => p.title && p.url); - // Filter to target venues and dedupe by URL - const filtered = mapped.filter((p) => isTargetVenue(p.venue)); - const seen = new Set(); - const deduped = filtered.filter((p) => { - const key = p.url; - if (seen.has(key)) return false; - seen.add(key); - return true; - }); - return deduped; -} - -export const semanticScholarTool = tool( - 'search-semanticscholar-vldb-sigmod', - 'Search Semantic Scholar for papers relevant to a query, constrained to VLDB (PVLDB) and SIGMOD venues', - { - query: z.string().describe('User research question or keywords'), - }, - async ({ query }) => { - const papers = await searchSemanticScholar(query); - return { - content: [{ - type: 'text' as const, - text: JSON.stringify({ papers }, null, 2), - }], - }; - } -); diff --git a/examples/claude-agent/research-assistant/src/utils/agentfs.ts b/examples/claude-agent/research-assistant/src/utils/agentfs.ts deleted file mode 100644 index 5bb79852..00000000 --- a/examples/claude-agent/research-assistant/src/utils/agentfs.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AgentFS } from 'agentfs-sdk'; - -let instance: AgentFS | null = null; - -export async function getAgentFS(): Promise { - if (!instance) { - const id = process.env.AGENTFS_ID || 'research-assistant'; - instance = await AgentFS.open({ id }); - } - return instance; -} diff --git a/examples/claude-agent/research-assistant/src/utils/pdf.ts b/examples/claude-agent/research-assistant/src/utils/pdf.ts deleted file mode 100644 index 91095dda..00000000 --- a/examples/claude-agent/research-assistant/src/utils/pdf.ts +++ /dev/null @@ -1,149 +0,0 @@ -import pdfParse from 'pdf-parse'; -import { getAgentFS } from './agentfs.js'; - -async function fetchHtmlWithFallback(url: string): Promise { - const res = await fetch(url, { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', - Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'Accept-Language': 'en-US,en;q=0.9', - Referer: url, - }, - }); - const text = await res.text(); - if (!res.ok || /Access Denied|Robot Check|unusual traffic/i.test(text)) { - try { - const u = new URL(url); - const proxy = `https://r.jina.ai/http://${u.host}${u.pathname}${u.search || ''}`; - const proxRes = await fetch(proxy, { headers: { Accept: 'text/plain' } }); - if (proxRes.ok) { - return await proxRes.text(); - } - } catch { - // ignore - } - } - return text; -} - -export async function fetchPdfText(pdfUrl: string, maxBytes = 20_000_000): Promise { - const res = await fetch(pdfUrl, { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', - Accept: 'application/pdf', - }, - }); - if (!res.ok) { - throw new Error(`PDF fetch failed: ${res.status}`); - } - const buf = Buffer.from(await res.arrayBuffer()); - if (buf.byteLength > maxBytes) { - throw new Error(`PDF too large: ${buf.byteLength} bytes`); - } - const parsed = await pdfParse(buf); - return parsed.text || ''; -} - -export async function fetchPdfTextCached(cacheKey: string, pdfUrl: string, maxBytes = 20_000_000): Promise { - const fs = (await getAgentFS()).fs; - const safeKey = cacheKey.replace(/[^a-z0-9-_]/gi, '_').toLowerCase().slice(0, 200); - const pdfPath = `/papers/${safeKey}.pdf`; - const txtPath = `/papers/${safeKey}.txt`; - // Try text cache - try { - const text = (await fs.readFile(txtPath, 'utf8')) as string; - if (text && text.length > 0) return text; - } catch { - // not cached - } - // Download PDF - const res = await fetch(pdfUrl, { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', - Accept: 'application/pdf', - Referer: pdfUrl, - }, - }); - if (!res.ok) { - throw new Error(`PDF fetch failed: ${res.status}`); - } - const buf = Buffer.from(await res.arrayBuffer()); - if (buf.byteLength > maxBytes) { - throw new Error(`PDF too large: ${buf.byteLength} bytes`); - } - // Cache PDF - try { - await fs.writeFile(pdfPath, buf); - } catch { - // ignore cache write error - } - const parsed = await pdfParse(buf); - const text = parsed.text || ''; - // Cache text - try { - await fs.writeFile(txtPath, text); - } catch { - // ignore - } - return text; -} - -export function clipText(text: string, maxChars = 8000): string { - if (text.length <= maxChars) return text; - return text.slice(0, maxChars); -} - -export async function resolvePdfUrl(landingUrl: string): Promise { - try { - const u = new URL(landingUrl); - // Direct PDF link - if (/\.(pdf)(\?|#|$)/i.test(u.pathname)) { - return landingUrl; - } - // arXiv: handle abs -> pdf and pdf without extension - if (u.host.includes('arxiv.org')) { - const absMatch = u.pathname.match(/^\/abs\/(.+)$/i); - if (absMatch && absMatch[1]) { - return `https://arxiv.org/pdf/${absMatch[1]}.pdf`; - } - const pdfMatch = u.pathname.match(/^\/pdf\/(.+)$/i); - if (pdfMatch && pdfMatch[1] && !pdfMatch[1].endsWith('.pdf')) { - return `https://arxiv.org/pdf/${pdfMatch[1]}.pdf`; - } - } - // ACM DL DOI landing page - if (u.host.includes('dl.acm.org') && /\/doi\//.test(u.pathname)) { - const html = await fetchHtmlWithFallback(landingUrl); - // 1) meta citation_pdf_url - const meta = html.match(/=2.0.0" - }, - "peerDependenciesMeta": { - "just-bash": { - "optional": true - } - } - }, - "node_modules/@ai-sdk/gateway": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.9.tgz", - "integrity": "sha512-EA5dZIukimwoJ9HIPuuREotAqaTItpdc/yImzVF0XGNg7B0YRJmYI8Uq3aCMr87vjr1YB1cWUfnrTt6OJ9eHiQ==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.2", - "@ai-sdk/provider-utils": "4.0.4", - "@vercel/oidc": "3.1.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/provider": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.2.tgz", - "integrity": "sha512-HrEmNt/BH/hkQ7zpi2o6N3k1ZR1QTb7z85WYhYygiTxOQuaml4CMtHCWRbric5WPU+RNsYI7r1EpyVQMKO1pYw==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/provider-utils": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.4.tgz", - "integrity": "sha512-VxhX0B/dWGbpNHxrKCWUAJKXIXV015J4e7qYjdIU9lLWeptk0KMLGcqkB4wFxff5Njqur8dt8wRi1MN9lZtDqg==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.2", - "@standard-schema/spec": "^1.1.0", - "eventsource-parser": "^3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@borewit/text-codec": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", - "integrity": "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@cloudflare/kv-asset-handler": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz", - "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==", - "dev": true, - "license": "MIT OR Apache-2.0", - "dependencies": { - "mime": "^3.0.0" - }, - "engines": { - "node": ">=16.13" - } - }, - "node_modules/@cloudflare/unenv-preset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz", - "integrity": "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg==", - "dev": true, - "license": "MIT OR Apache-2.0", - "peerDependencies": { - "unenv": "2.0.0-rc.14", - "workerd": "^1.20250124.0" - }, - "peerDependenciesMeta": { - "workerd": { - "optional": true - } - } - }, - "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250718.0.tgz", - "integrity": "sha512-FHf4t7zbVN8yyXgQ/r/GqLPaYZSGUVzeR7RnL28Mwj2djyw2ZergvytVc7fdGcczl6PQh+VKGfZCfUqpJlbi9g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250718.0.tgz", - "integrity": "sha512-fUiyUJYyqqp4NqJ0YgGtp4WJh/II/YZsUnEb6vVy5Oeas8lUOxnN+ZOJ8N/6/5LQCVAtYCChRiIrBbfhTn5Z8Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250718.0.tgz", - "integrity": "sha512-5+eb3rtJMiEwp08Kryqzzu8d1rUcK+gdE442auo5eniMpT170Dz0QxBrqkg2Z48SFUPYbj+6uknuA5tzdRSUSg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250718.0.tgz", - "integrity": "sha512-Aa2M/DVBEBQDdATMbn217zCSFKE+ud/teS+fFS+OQqKABLn0azO2qq6ANAHYOIE6Q3Sq4CxDIQr8lGdaJHwUog==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250718.0.tgz", - "integrity": "sha512-dY16RXKffmugnc67LTbyjdDHZn5NoTF1yHEf2fN4+OaOnoGSp3N1x77QubTDwqZ9zECWxgQfDLjddcH8dWeFhg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workers-types": { - "version": "4.20260107.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260107.1.tgz", - "integrity": "sha512-HErxm1gnfkhO1kUEtFoQuKEiAkoS+ywqcfk5cq+xG+YomF5oCjMAjuqF6WLbR0VhHkYLERhbUSYnQ5Q/RB0T2g==", - "dev": true, - "license": "MIT OR Apache-2.0" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild-plugins/node-globals-polyfill": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", - "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "esbuild": "*" - } - }, - "node_modules/@esbuild-plugins/node-modules-polyfill": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", - "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", - "dev": true, - "license": "ISC", - "dependencies": { - "escape-string-regexp": "^4.0.0", - "rollup-plugin-node-polyfills": "^0.2.1" - }, - "peerDependencies": { - "esbuild": "*" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.2.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@mixmark-io/domino": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", - "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==", - "license": "BSD-2-Clause" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "license": "MIT" - }, - "node_modules/@vercel/oidc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz", - "integrity": "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==", - "license": "Apache-2.0", - "engines": { - "node": ">= 20" - } - }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agentfs-sdk": { - "resolved": "../../sdk/typescript", - "link": true - }, - "node_modules/ai": { - "version": "6.0.18", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.18.tgz", - "integrity": "sha512-WyscdyO+79MH46xDOPfT+CWEzzMl8MMJqVSw9y3VKjQB1hvO6pSO3YibHO3pqHZLsUjANyHGmyIhx72pDH9+qA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/gateway": "3.0.9", - "@ai-sdk/provider": "3.0.2", - "@ai-sdk/provider-utils": "4.0.4", - "@opentelemetry/api": "1.9.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/as-table": { - "version": "1.0.55", - "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", - "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "printable-characters": "^1.0.42" - } - }, - "node_modules/bash-tool": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bash-tool/-/bash-tool-1.0.0.tgz", - "integrity": "sha512-GO8P1dVZnBPqZvjv5lXFtdZGDR9BM0A4UmE3NCfbUhr7nVv2K0qwDN/tPokaG839UcsZocGeU+Z/ELmFpqxRYw==", - "license": "MIT", - "dependencies": { - "fast-glob": "^3.3.2", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@vercel/sandbox": "*", - "ai": "^6.0.0", - "just-bash": "*" - }, - "peerDependenciesMeta": { - "@vercel/sandbox": { - "optional": true - }, - "just-bash": { - "optional": true - } - } - }, - "node_modules/bash-tool/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/blake3-wasm": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", - "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", - "dev": true, - "license": "MIT" - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", - "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/exit-hook": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", - "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-xml-parser": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.3.tgz", - "integrity": "sha512-2O3dkPAAC6JavuMm8+4+pgTk+5hoAs+CjZ+sWcQLkX9+/tHRuTkQh/Oaifr8qDmZ8iEHb771Ea6G8CdwkrgvYA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-type": { - "version": "21.3.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.0.tgz", - "integrity": "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==", - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-source": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", - "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", - "dev": true, - "license": "Unlicense", - "dependencies": { - "data-uri-to-buffer": "^2.0.0", - "source-map": "^0.6.1" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ini": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", - "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/is-arrayish": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", - "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/just-bash": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/just-bash/-/just-bash-2.1.0.tgz", - "integrity": "sha512-fFIC5HpOWmgHl8vcsyQr1SCfQ8LanwRAFUp4xs4pwTRClIEusIuL2AXAIrBRffQSqT5kYheLJVLvt+/8SlIB7g==", - "license": "Apache-2.0", - "dependencies": { - "diff": "^8.0.2", - "fast-xml-parser": "^5.3.3", - "file-type": "^21.2.0", - "ini": "^6.0.0", - "minimatch": "^10.1.1", - "papaparse": "^5.5.3", - "smol-toml": "^1.6.0", - "sprintf-js": "^1.1.3", - "turndown": "^7.2.2", - "yaml": "^2.8.2" - }, - "bin": { - "just-bash": "dist/bin/just-bash.js", - "just-bash-shell": "dist/bin/shell/shell.js" - } - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/miniflare": { - "version": "3.20250718.3", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250718.3.tgz", - "integrity": "sha512-JuPrDJhwLrNLEJiNLWO7ZzJrv/Vv9kZuwMYCfv0LskQDM6Eonw4OvywO3CH/wCGjgHzha/qyjUh8JQ068TjDgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "0.8.1", - "acorn": "8.14.0", - "acorn-walk": "8.3.2", - "exit-hook": "2.2.1", - "glob-to-regexp": "0.4.1", - "stoppable": "1.1.0", - "undici": "^5.28.5", - "workerd": "1.20250718.0", - "ws": "8.18.0", - "youch": "3.3.4", - "zod": "3.22.3" - }, - "bin": { - "miniflare": "bootstrap.js" - }, - "engines": { - "node": ">=16.13" - } - }, - "node_modules/miniflare/node_modules/zod": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", - "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "dev": true, - "license": "MIT", - "bin": { - "mustache": "bin/mustache" - } - }, - "node_modules/ohash": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/papaparse": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", - "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/printable-characters": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", - "dev": true, - "license": "Unlicense" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup-plugin-inject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", - "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", - "dev": true, - "license": "MIT", - "dependencies": { - "estree-walker": "^0.6.1", - "magic-string": "^0.25.3", - "rollup-pluginutils": "^2.8.1" - } - }, - "node_modules/rollup-plugin-node-polyfills": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", - "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", - "dev": true, - "license": "MIT", - "dependencies": { - "rollup-plugin-inject": "^3.0.0" - } - }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "estree-walker": "^0.6.1" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", - "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/smol-toml": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", - "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 18" - }, - "funding": { - "url": "https://github.com/sponsors/cyyynthia" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true, - "license": "MIT" - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "license": "BSD-3-Clause" - }, - "node_modules/stacktracey": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", - "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", - "dev": true, - "license": "Unlicense", - "dependencies": { - "as-table": "^1.0.36", - "get-source": "^2.0.12" - } - }, - "node_modules/stoppable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", - "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4", - "npm": ">=6" - } - }, - "node_modules/strnum": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", - "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/strtok3": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.2.1", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/turndown": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.2.tgz", - "integrity": "sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==", - "license": "MIT", - "dependencies": { - "@mixmark-io/domino": "^2.2.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.2.tgz", - "integrity": "sha512-heMioaxBcG9+Znsda5Q8sQbWnLJSl98AFDXTO80wELWEzX3hordXsTdxrIfMQoO9IY1MEnoGoPjpoKpMj+Yx0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/unenv": { - "version": "2.0.0-rc.14", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.14.tgz", - "integrity": "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "defu": "^6.1.4", - "exsolve": "^1.0.1", - "ohash": "^2.0.10", - "pathe": "^2.0.3", - "ufo": "^1.5.4" - } - }, - "node_modules/workerd": { - "version": "1.20250718.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250718.0.tgz", - "integrity": "sha512-kqkIJP/eOfDlUyBzU7joBg+tl8aB25gEAGqDap+nFWb+WHhnooxjGHgxPBy3ipw2hnShPFNOQt5lFRxbwALirg==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "bin": { - "workerd": "bin/workerd" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20250718.0", - "@cloudflare/workerd-darwin-arm64": "1.20250718.0", - "@cloudflare/workerd-linux-64": "1.20250718.0", - "@cloudflare/workerd-linux-arm64": "1.20250718.0", - "@cloudflare/workerd-windows-64": "1.20250718.0" - } - }, - "node_modules/workers-ai-provider": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/workers-ai-provider/-/workers-ai-provider-3.0.2.tgz", - "integrity": "sha512-HUPu5aQrPOHHaoBiRoUiNzK5wtVtCFl0g/OO3gk9wguGEUK4m81qrqmdte6xlkWeKtujCHIDfIdIN6rZAnzVew==", - "license": "MIT", - "peerDependencies": { - "@ai-sdk/provider": "^3.0.0", - "@ai-sdk/provider-utils": "^4.0.0", - "ai": "^6.0.0", - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/wrangler": { - "version": "3.114.16", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.114.16.tgz", - "integrity": "sha512-ve/ULRjrquu5BHNJ+1T0ipJJlJ6pD7qLmhwRkk0BsUIxatNe4HP4odX/R4Mq/RHG6LOnVAFs7SMeSHlz/1mNlQ==", - "dev": true, - "license": "MIT OR Apache-2.0", - "dependencies": { - "@cloudflare/kv-asset-handler": "0.3.4", - "@cloudflare/unenv-preset": "2.0.2", - "@esbuild-plugins/node-globals-polyfill": "0.2.3", - "@esbuild-plugins/node-modules-polyfill": "0.2.2", - "blake3-wasm": "2.1.5", - "esbuild": "0.17.19", - "miniflare": "3.20250718.3", - "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.14", - "workerd": "1.20250718.0" - }, - "bin": { - "wrangler": "bin/wrangler.js", - "wrangler2": "bin/wrangler.js" - }, - "engines": { - "node": ">=16.17.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2", - "sharp": "^0.33.5" - }, - "peerDependencies": { - "@cloudflare/workers-types": "^4.20250408.0" - }, - "peerDependenciesMeta": { - "@cloudflare/workers-types": { - "optional": true - } - } - }, - "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/youch": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", - "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cookie": "^0.7.1", - "mustache": "^4.2.0", - "stacktracey": "^2.1.8" - } - }, - "node_modules/zod": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", - "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/examples/cloudflare/package.json b/examples/cloudflare/package.json deleted file mode 100644 index a75cb8ca..00000000 --- a/examples/cloudflare/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "cloudflare-agentfs-example", - "version": "1.0.0", - "description": "AI Agent with AgentFS on Cloudflare Workers", - "type": "module", - "scripts": { - "dev": "wrangler dev", - "deploy": "wrangler deploy", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "agentfs-sdk": "file:../../sdk/typescript", - "ai": "^6.0.0", - "just-bash": "^2.0.0", - "bash-tool": "^1.0.0", - "workers-ai-provider": "^3.0.0" - }, - "devDependencies": { - "@cloudflare/workers-types": "^4.20241218.0", - "typescript": "^5.3.0", - "wrangler": "^3.99.0" - } -} diff --git a/examples/cloudflare/src/index.ts b/examples/cloudflare/src/index.ts deleted file mode 100644 index 68d474fd..00000000 --- a/examples/cloudflare/src/index.ts +++ /dev/null @@ -1,111 +0,0 @@ -/** - * AI Agent with AgentFS on Cloudflare Workers - * - * This example shows how to run an AI agent with just-bash and AgentFS - * on Cloudflare Workers using Durable Objects for persistent storage - * and Workers AI for inference. - * - * Deploy: - * npm install - * npm run deploy - * - * Then send prompts: - * curl -X POST https://your-worker.workers.dev/chat \ - * -H "Content-Type: application/json" \ - * -d '{"message": "List all files in the root directory"}' - */ - -import { DurableObject } from "cloudflare:workers"; -import { streamText, stepCountIs } from "ai"; -import { createWorkersAI } from "workers-ai-provider"; -import { createBashTool } from "bash-tool"; -import { Bash } from "just-bash"; -import { AgentFS, type CloudflareStorage } from "agentfs-sdk/cloudflare"; - -export interface Env { - AGENT_FS: DurableObjectNamespace; - AI: Ai; -} - -/** - * Durable Object that provides an AI agent with persistent filesystem. - */ -export class AgentFSDurableObject extends DurableObject { - private fs: AgentFS; - - constructor(ctx: DurableObjectState, env: Env) { - super(ctx, env); - this.fs = AgentFS.create(ctx.storage as CloudflareStorage); - } - - async chat(message: string): Promise> { - // Create a just-bash Bash instance with AgentFS filesystem - const bash = new Bash({ fs: this.fs }); - - // Create the bash tool with the just-bash sandbox - const bashToolkit = await createBashTool({ - sandbox: bash, - extraInstructions: `You are an AI agent with a persistent filesystem. -Files you create will persist across sessions. - -Use bash commands to interact with the filesystem: -- ls to list files -- cat to read files -- echo "content" > file.txt to write files -- mkdir to create directories -- rm to remove files`, - }); - - // Create Workers AI provider - const workersai = createWorkersAI({ binding: this.env.AI }); - - // Stream the response - const result = streamText({ - model: workersai("@cf/meta/llama-3.1-70b-instruct" as Parameters[0]), - tools: bashToolkit.tools, - messages: [{ role: "user", content: message }], - stopWhen: stepCountIs(10), - }); - - return result.toTextStreamResponse().body!; - } -} - -export default { - async fetch(request: Request, env: Env): Promise { - const url = new URL(request.url); - - if (url.pathname === "/chat" && request.method === "POST") { - const { message } = await request.json<{ message: string }>(); - - if (!message) { - return new Response("Missing message", { status: 400 }); - } - - // Route to the Durable Object - const id = env.AGENT_FS.idFromName("default"); - const stub = env.AGENT_FS.get(id); - const stream = await stub.chat(message); - - return new Response(stream, { - headers: { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - }, - }); - } - - return new Response( - `AI Agent with AgentFS on Cloudflare - -POST /chat - Send a message to the agent - -Example: - curl -X POST ${url.origin}/chat \\ - -H "Content-Type: application/json" \\ - -d '{"message": "Create a file called hello.txt with Hello World"}' -`, - { headers: { "Content-Type": "text/plain" } } - ); - }, -}; diff --git a/examples/cloudflare/tsconfig.json b/examples/cloudflare/tsconfig.json deleted file mode 100644 index 3d8fd390..00000000 --- a/examples/cloudflare/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "lib": ["ES2022"], - "types": ["@cloudflare/workers-types"] - }, - "include": ["src/**/*.ts"] -} diff --git a/examples/cloudflare/wrangler.toml b/examples/cloudflare/wrangler.toml deleted file mode 100644 index 76fc366f..00000000 --- a/examples/cloudflare/wrangler.toml +++ /dev/null @@ -1,14 +0,0 @@ -name = "agentfs-cloudflare-example" -main = "src/index.ts" -compatibility_date = "2024-12-18" - -[ai] -binding = "AI" - -[[durable_objects.bindings]] -name = "AGENT_FS" -class_name = "AgentFSDurableObject" - -[[migrations]] -tag = "v1" -new_sqlite_classes = ["AgentFSDurableObject"] diff --git a/examples/firecracker/.gitignore b/examples/firecracker/.gitignore deleted file mode 100644 index 32ad8752..00000000 --- a/examples/firecracker/.gitignore +++ /dev/null @@ -1,20 +0,0 @@ -# Built artifacts -linux-amazon/ -linux-6.1.*/ -busybox-*/ -vmlinux -vmlinux-* -rootfs.ext4 -firecracker -*.tar.xz -*.tar.gz -*.tar.bz2 - -# Config files generated at runtime -vm_config.json -test_config.json -test_disk.raw -test_disk_config.json - -# Mount points -rootfs_mnt/ diff --git a/examples/firecracker/README.md b/examples/firecracker/README.md deleted file mode 100644 index 41de3d9d..00000000 --- a/examples/firecracker/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Firecracker with AgentFS Example - -A minimal Firecracker VM setup with NFSv3 support for AgentFS. - -## Prerequisites - -- Linux host with KVM support -- Firecracker binary installed -- Build tools: `gcc`, `make`, `curl`, `sudo` - -## Build - -1. Build the Linux kernel with NFSv3 support: - -```bash -./build-kernel.sh -``` - -2. Build the rootfs with BusyBox: - -```bash -./build-rootfs.sh -``` - -## Run - -Start the Firecracker VM: - -```bash -./run.sh -``` - -Press `Ctrl+C` to stop the VM. - -## Files - -- `build-kernel.sh` - Downloads and builds Linux kernel 6.1.x with NFSv3 support -- `build-rootfs.sh` - Creates minimal ext4 rootfs with BusyBox -- `run.sh` - Launches Firecracker VM with the built kernel and rootfs diff --git a/examples/firecracker/build-kernel.sh b/examples/firecracker/build-kernel.sh deleted file mode 100755 index 6dab7fcf..00000000 --- a/examples/firecracker/build-kernel.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -set -e - -KERNEL_DIR="linux-amazon" - -# Clone Amazon Linux kernel if not present -if [ ! -d "${KERNEL_DIR}" ]; then - echo "Cloning Amazon Linux kernel..." - git clone --depth 1 --branch microvm-kernel-6.1.102-1.182.amzn2023 \ - https://github.com/amazonlinux/linux.git "${KERNEL_DIR}" -fi - -cd "${KERNEL_DIR}" - -# Download Firecracker's kernel config -echo "Downloading Firecracker kernel config..." -curl -L -o .config "https://raw.githubusercontent.com/firecracker-microvm/firecracker/refs/heads/firecracker-v1.10/resources/guest_configs/microvm-kernel-ci-x86_64-6.1.config" - -# Disable certificate/keyring stuff that requires OpenSSL -./scripts/config --disable SYSTEM_TRUSTED_KEYRING -./scripts/config --disable SECONDARY_TRUSTED_KEYRING -./scripts/config --disable SYSTEM_REVOCATION_KEYS -./scripts/config --disable MODULE_SIG -./scripts/config --disable INTEGRITY -./scripts/config --disable IMA -./scripts/config --disable EVM -./scripts/config --set-str SYSTEM_TRUSTED_KEYS "" -./scripts/config --set-str SYSTEM_REVOCATION_KEYS "" - -# Enable virtio-mmio cmdline devices (needed for Firecracker to register devices) -./scripts/config --enable VIRTIO_MMIO_CMDLINE_DEVICES -./scripts/config --disable BLK_DEV_INTEGRITY - -# Enable NFSv3 client support -./scripts/config --enable NFS_V3 -./scripts/config --enable NFS_V3_ACL -./scripts/config --enable LOCKD_V4 - -# Update config -make olddefconfig - -echo "Building kernel..." -# Use C11 to avoid C23 keyword conflicts with older kernel versions -make -j$(nproc) vmlinux CC="gcc -std=gnu11" - -echo "Done! Kernel built at: ${KERNEL_DIR}/vmlinux" -cd .. -ln -sf "${KERNEL_DIR}/vmlinux" vmlinux -echo "Symlinked to: vmlinux" diff --git a/examples/firecracker/build-rootfs.sh b/examples/firecracker/build-rootfs.sh deleted file mode 100755 index 2021429c..00000000 --- a/examples/firecracker/build-rootfs.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/bash -set -e - -BUSYBOX_VERSION="1.36.1" -BUSYBOX_DIR="busybox-${BUSYBOX_VERSION}" -BUSYBOX_MIRROR="https://github.com/mirror/busybox/archive/refs/tags/${BUSYBOX_VERSION//./_}.tar.gz" -ROOTFS_DIR="rootfs" - -# Download busybox if not present -if [ ! -d "${BUSYBOX_DIR}" ]; then - echo "Downloading BusyBox ${BUSYBOX_VERSION}..." - curl -L --connect-timeout 10 -o busybox.tar.gz "${BUSYBOX_MIRROR}" - tar xf busybox.tar.gz - mv "busybox-${BUSYBOX_VERSION//./_}" "${BUSYBOX_DIR}" - rm busybox.tar.gz -fi - -# Build busybox statically if not already built -if [ ! -f "${BUSYBOX_DIR}/_install/bin/busybox" ]; then - cd "${BUSYBOX_DIR}" - echo "Configuring BusyBox..." - make defconfig - sed -i 's/# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config - sed -i 's/CONFIG_TC=y/# CONFIG_TC is not set/' .config - echo "Building BusyBox..." - make -j$(nproc) - make install - cd .. -fi - -echo "Creating rootfs..." - -# Create directory structure -rm -rf "${ROOTFS_DIR}" -mkdir -p "${ROOTFS_DIR}"/{bin,sbin,etc,proc,sys,dev,tmp,root,var/log,mnt} - -# Copy busybox -cp -a "${BUSYBOX_DIR}/_install/"* "${ROOTFS_DIR}/" - -# Create init script -cat > "${ROOTFS_DIR}/init" << 'INITEOF' -#!/bin/sh -mount -t proc proc /proc -mount -t sysfs sysfs /sys -mount -t devtmpfs devtmpfs /dev 2>/dev/null || true -hostname agentfs - -cat << 'EOF' - - _ _ _____ ____ - / \ __ _ ___ _ __ | |_| ___/ ___| - / _ \ / _` |/ _ \ '_ \| __| |_ \___ \ - / ___ \ (_| | __/ | | | |_| _| ___) | - /_/ \_\__, |\___|_| |_|\__|_| |____/ - |___/ - - Welcome to AgentFS + Firecracker! - - NFS root from host. Changes stored in SQLite. - -EOF - -exec /bin/sh -INITEOF -chmod +x "${ROOTFS_DIR}/init" - -# Create /etc/passwd and /etc/group -echo "root:x:0:0:root:/root:/bin/sh" > "${ROOTFS_DIR}/etc/passwd" -echo "root:x:0:" > "${ROOTFS_DIR}/etc/group" - -echo "Done! Rootfs created at: ${ROOTFS_DIR}/" diff --git a/examples/firecracker/firecracker.sh b/examples/firecracker/firecracker.sh deleted file mode 100755 index 9a1cae3e..00000000 --- a/examples/firecracker/firecracker.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/bash -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -FIRECRACKER="${SCRIPT_DIR}/firecracker" -FC_VERSION="v1.14.0" -TAP_DEV="fc-tap0" -TAP_IP="172.16.0.1" -VM_IP="172.16.0.2" -NFS_PORT="11111" -AGENTFS="${SCRIPT_DIR}/../../cli/target/release/agentfs" -AGENT_ID="${1:-firecracker}" -DB_PATH="${SCRIPT_DIR}/.agentfs/${AGENT_ID}.db" - -# Download Firecracker if not present -if [ ! -f "${FIRECRACKER}" ]; then - echo "Downloading Firecracker ${FC_VERSION}..." - curl -L -o firecracker.tgz "https://github.com/firecracker-microvm/firecracker/releases/download/${FC_VERSION}/firecracker-${FC_VERSION}-x86_64.tgz" - tar xf firecracker.tgz - mv "release-${FC_VERSION}-x86_64/firecracker-${FC_VERSION}-x86_64" "${FIRECRACKER}" - rm -rf "release-${FC_VERSION}-x86_64" firecracker.tgz - chmod +x "${FIRECRACKER}" -fi - -# Check prerequisites -if [ ! -f "${SCRIPT_DIR}/linux-amazon/vmlinux" ]; then - echo "Error: Kernel not found. Run ./build-kernel.sh first." - exit 1 -fi - -if [ ! -d "${SCRIPT_DIR}/rootfs" ]; then - echo "Error: rootfs not found. Run ./create-nfs-rootfs.sh first." - exit 1 -fi - -if [ ! -f "${AGENTFS}" ]; then - echo "Error: agentfs binary not found. Build it first:" - echo " cd ../../cli && cargo build --release" - exit 1 -fi - -# Initialize agentfs database if needed -if [ ! -f "${DB_PATH}" ]; then - cd "${SCRIPT_DIR}" - ${AGENTFS} init --base ./rootfs "${AGENT_ID}" >/dev/null -fi - -# Clean up function -cleanup() { - kill $AGENTFS_PID 2>/dev/null || true - sudo ip link del "${TAP_DEV}" 2>/dev/null || true - echo "" - echo "Changes saved. View with: agentfs diff ${AGENT_ID}" -} -trap cleanup EXIT - -# Clean up from previous failed run -sudo ip link del "${TAP_DEV}" 2>/dev/null || true -pkill -f "agentfs serve nfs.*${NFS_PORT}" 2>/dev/null || true -sleep 0.5 - -# Set up TAP device -sudo ip tuntap add dev "${TAP_DEV}" mode tap -sudo ip addr add "${TAP_IP}/24" dev "${TAP_DEV}" -sudo ip link set "${TAP_DEV}" up - -# Start agentfs NFS server (suppress output) -cd "${SCRIPT_DIR}" -${AGENTFS} serve nfs --bind "${TAP_IP}" --port "${NFS_PORT}" "${AGENT_ID}" >/dev/null 2>&1 & -AGENTFS_PID=$! -sleep 1 - -if ! kill -0 $AGENTFS_PID 2>/dev/null; then - echo "Error: agentfs NFS server failed to start" - exit 1 -fi - -# Create Firecracker config -KERNEL_PATH="${SCRIPT_DIR}/linux-amazon/vmlinux" -cat > "${SCRIPT_DIR}/vm_config.json" << EOF -{ - "boot-source": { - "kernel_image_path": "${KERNEL_PATH}", - "boot_args": "console=ttyS0 reboot=k panic=1 pci=off quiet loglevel=0 ip=${VM_IP}::${TAP_IP}:255.255.255.0::eth0:off root=/dev/nfs nfsroot=${TAP_IP}:/,nfsvers=3,tcp,nolock,port=${NFS_PORT},mountport=${NFS_PORT} rw init=/init" - }, - "drives": [], - "network-interfaces": [ - { - "iface_id": "eth0", - "guest_mac": "AA:FC:00:00:00:01", - "host_dev_name": "${TAP_DEV}" - } - ], - "machine-config": { - "vcpu_count": 1, - "mem_size_mib": 256 - } -} -EOF - -# Run Firecracker -${FIRECRACKER} --no-api --config-file "${SCRIPT_DIR}/vm_config.json" --level Error diff --git a/examples/mastra/research-assistant/.gitignore b/examples/mastra/research-assistant/.gitignore deleted file mode 100644 index 139896d7..00000000 --- a/examples/mastra/research-assistant/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -output.txt -node_modules -dist -.mastra -.env.development -.env -*.db -*.db-* - -# AgentFS local databases -.agentfs/ diff --git a/examples/mastra/research-assistant/README.md b/examples/mastra/research-assistant/README.md deleted file mode 100644 index c55ab403..00000000 --- a/examples/mastra/research-assistant/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Research Assistant (VLDB + SIGMOD) - -A minimal research assistant that answers database research questions by finding most relevant papers from the VLDB and SIGMOD conference proceedings. - -## Getting Started - -Install dependencies: - -```console -npm install -``` - -Ask a research question: - -``` -npm run ask -- "How do learned indexes compare to B+ trees?" -``` diff --git a/examples/mastra/research-assistant/cli/ask.ts b/examples/mastra/research-assistant/cli/ask.ts deleted file mode 100644 index 0952ee71..00000000 --- a/examples/mastra/research-assistant/cli/ask.ts +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env node -import 'dotenv/config'; -import { mastra } from '../src/mastra/index.js'; -import { getAgentFS } from '../src/mastra/utils/agentfs.js'; - -async function main() { - const args = process.argv.slice(2); - const question = args.join(' ').trim(); - - if (!question) { - console.error('Usage: npm run ask -- ""'); - process.exit(1); - } - - try { - // Initialize agentfs - const agentfs = await getAgentFS(); - - // Get the agent - const agent = mastra.getAgent('researchAgent'); - if (!agent) { - console.error('Research agent not available.'); - process.exit(1); - } - - // Simply ask the agent - it has all the logic and tools it needs - const response = await agent.generate([ - { - role: 'user', - content: question, - }, - ]); - - // Output the agent's response - console.log(response.text); - - } catch (err: any) { - console.error('Error:', err?.message || err); - process.exit(1); - } -} - -main(); - - diff --git a/examples/mastra/research-assistant/package-lock.json b/examples/mastra/research-assistant/package-lock.json deleted file mode 100644 index 404752b6..00000000 --- a/examples/mastra/research-assistant/package-lock.json +++ /dev/null @@ -1,10040 +0,0 @@ -{ - "name": "research-assistant", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "research-assistant", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@mastra/core": "^0.24.0", - "@mastra/evals": "^0.14.3", - "@mastra/libsql": "^0.16.2", - "@mastra/loggers": "^0.10.19", - "@mastra/memory": "^0.15.11", - "agentfs-sdk": "file:../../../sdk/typescript", - "pdf-parse": "^1.1.1", - "zod": "^4.1.12" - }, - "devDependencies": { - "@types/node": "^24.10.0", - "mastra": "^0.18.0", - "tsx": "^4.19.1", - "typescript": "^5.9.3" - }, - "engines": { - "node": ">=20.9.0" - } - }, - "../../../sdk/typescript": { - "name": "agentfs-sdk", - "version": "0.1.0", - "license": "MIT", - "dependencies": { - "@tursodatabase/database": "^0.3.2" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "@vitest/ui": "^4.0.1", - "typescript": "^5.3.0", - "vitest": "^4.0.1" - } - }, - "node_modules/@a2a-js/sdk": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@a2a-js/sdk/-/sdk-0.2.5.tgz", - "integrity": "sha512-VTDuRS5V0ATbJ/LkaQlisMnTAeYKXAK6scMguVBstf+KIBQ7HIuKhiXLv+G/hvejkV+THoXzoNifInAkU81P1g==", - "dependencies": { - "@types/cors": "^2.8.17", - "@types/express": "^4.17.23", - "body-parser": "^2.2.0", - "cors": "^2.8.5", - "express": "^4.21.2", - "uuid": "^11.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/anthropic-v5": { - "name": "@ai-sdk/anthropic", - "version": "2.0.33", - "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-2.0.33.tgz", - "integrity": "sha512-egqr9PHqqX2Am5mn/Xs1C3+1/wphVKiAjpsVpW85eLc2WpW7AgiAg52DCBr4By9bw3UVVuMeR4uEO1X0dKDUDA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.12" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/anthropic-v5/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/gateway": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.0.tgz", - "integrity": "sha512-Gj0PuawK7NkZuyYgO/h5kDK/l6hFOjhLdTq3/Lli1FTl47iGmwhH1IZQpAL3Z09BeFYWakcwUmn02ovIm2wy9g==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.12", - "@vercel/oidc": "3.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/google-v5": { - "name": "@ai-sdk/google", - "version": "2.0.23", - "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.23.tgz", - "integrity": "sha512-VbCnKR+6aWUVLkAiSW5gUEtST7KueEmlt+d6qwDikxlLnFG9pzy59je8MiDVeM5G2tuSXbvZQF78PGIfXDBmow==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.12" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/google-v5/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/openai-compatible": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-1.0.22.tgz", - "integrity": "sha512-Q+lwBIeMprc/iM+vg1yGjvzRrp74l316wDpqWdbmd4VXXlllblzGsUgBLTeKvcEapFTgqk0FRETvSb58Y6dsfA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.12" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/openai-compatible-v5": { - "name": "@ai-sdk/openai-compatible", - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-1.0.22.tgz", - "integrity": "sha512-Q+lwBIeMprc/iM+vg1yGjvzRrp74l316wDpqWdbmd4VXXlllblzGsUgBLTeKvcEapFTgqk0FRETvSb58Y6dsfA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.12" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/openai-compatible-v5/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/openai-v5": { - "name": "@ai-sdk/openai", - "version": "2.0.53", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-2.0.53.tgz", - "integrity": "sha512-GIkR3+Fyif516ftXv+YPSPstnAHhcZxNoR2s8uSHhQ1yBT7I7aQYTVwpjAuYoT3GR+TeP50q7onj2/nDRbT2FQ==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.12" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/openai-v5/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/provider": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz", - "integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/provider-utils": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.12.tgz", - "integrity": "sha512-ZtbdvYxdMoria+2SlNarEk6Hlgyf+zzcznlD55EAl+7VZvJaSg2sqPvwArY7L6TfDEDJsnCq0fdhBSkYo0Xqdg==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/provider-utils-v5": { - "name": "@ai-sdk/provider-utils", - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.12.tgz", - "integrity": "sha512-ZtbdvYxdMoria+2SlNarEk6Hlgyf+zzcznlD55EAl+7VZvJaSg2sqPvwArY7L6TfDEDJsnCq0fdhBSkYo0Xqdg==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/provider-utils-v5/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/provider-utils/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/provider-v5": { - "name": "@ai-sdk/provider", - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/xai-v5": { - "name": "@ai-sdk/xai", - "version": "2.0.26", - "resolved": "https://registry.npmjs.org/@ai-sdk/xai/-/xai-2.0.26.tgz", - "integrity": "sha512-+VtaLZSxmoKnNeJGM9bbtbZ3QMkPFlBB4N8prngbrSnvU/hG8cNdvvSBW/rIk6/DHrc2R8nFntNIBQoIRuBdQw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/openai-compatible": "1.0.22", - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.12" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/xai-v5/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.9.3", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", - "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", - "license": "MIT", - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", - "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.5", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", - "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", - "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@clack/core": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz", - "integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } - }, - "node_modules/@clack/prompts": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz", - "integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@clack/core": "0.5.0", - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@expo/devcert": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@expo/devcert/-/devcert-1.2.0.tgz", - "integrity": "sha512-Uilcv3xGELD5t/b0eM4cxBFEKQRIivB3v7i+VhWLV/gL98aw810unLKKJbGAxAIhY6Ipyz8ChWibFsKFXYwstA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@expo/sudo-prompt": "^9.3.1", - "debug": "^3.1.0", - "glob": "^10.4.2" - } - }, - "node_modules/@expo/devcert/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/@expo/sudo-prompt": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/@expo/sudo-prompt/-/sudo-prompt-9.3.2.tgz", - "integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@grpc/grpc-js": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.1.tgz", - "integrity": "sha512-sPxgEWtPUR3EnRJCEtbGZG2iX8LQDUls2wUS3o27jg07KqJFMq6YDeWvMo1wfpmy3rqRdS0rivpLwhqQtEyCuQ==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/proto-loader": "^0.8.0", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", - "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.5.3", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/ttlcache": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", - "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "license": "MIT" - }, - "node_modules/@libsql/client": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.15.15.tgz", - "integrity": "sha512-twC0hQxPNHPKfeOv3sNT6u2pturQjLcI+CnpTM0SjRpocEGgfiZ7DWKXLNnsothjyJmDqEsBQJ5ztq9Wlu470w==", - "license": "MIT", - "dependencies": { - "@libsql/core": "^0.15.14", - "@libsql/hrana-client": "^0.7.0", - "js-base64": "^3.7.5", - "libsql": "^0.5.22", - "promise-limit": "^2.7.0" - } - }, - "node_modules/@libsql/core": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.15.15.tgz", - "integrity": "sha512-C88Z6UKl+OyuKKPwz224riz02ih/zHYI3Ho/LAcVOgjsunIRZoBw7fjRfaH9oPMmSNeQfhGklSG2il1URoOIsA==", - "license": "MIT", - "dependencies": { - "js-base64": "^3.7.5" - } - }, - "node_modules/@libsql/darwin-arm64": { - "version": "0.5.22", - "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.22.tgz", - "integrity": "sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@libsql/darwin-x64": { - "version": "0.5.22", - "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.22.tgz", - "integrity": "sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@libsql/hrana-client": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", - "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", - "license": "MIT", - "dependencies": { - "@libsql/isomorphic-fetch": "^0.3.1", - "@libsql/isomorphic-ws": "^0.1.5", - "js-base64": "^3.7.5", - "node-fetch": "^3.3.2" - } - }, - "node_modules/@libsql/hrana-client/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/@libsql/isomorphic-fetch": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", - "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@libsql/isomorphic-ws": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", - "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", - "license": "MIT", - "dependencies": { - "@types/ws": "^8.5.4", - "ws": "^8.13.0" - } - }, - "node_modules/@libsql/linux-arm-gnueabihf": { - "version": "0.5.22", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.22.tgz", - "integrity": "sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-arm-musleabihf": { - "version": "0.5.22", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.22.tgz", - "integrity": "sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-arm64-gnu": { - "version": "0.5.22", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.22.tgz", - "integrity": "sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-arm64-musl": { - "version": "0.5.22", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.22.tgz", - "integrity": "sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-x64-gnu": { - "version": "0.5.22", - "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.22.tgz", - "integrity": "sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-x64-musl": { - "version": "0.5.22", - "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.22.tgz", - "integrity": "sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/win32-x64-msvc": { - "version": "0.5.22", - "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.22.tgz", - "integrity": "sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@mastra/core": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@mastra/core/-/core-0.24.0.tgz", - "integrity": "sha512-lurtcdObDJE1Hb6p6Byu67i2mnNFVSE1ylVnbtcAqSXmfW7rRNx61B+inJmVSMZOu2gVtfoH3Q0tMrjBsJwm6g==", - "license": "Apache-2.0", - "dependencies": { - "@a2a-js/sdk": "~0.2.4", - "@ai-sdk/anthropic-v5": "npm:@ai-sdk/anthropic@2.0.33", - "@ai-sdk/google-v5": "npm:@ai-sdk/google@2.0.23", - "@ai-sdk/openai-compatible-v5": "npm:@ai-sdk/openai-compatible@1.0.22", - "@ai-sdk/openai-v5": "npm:@ai-sdk/openai@2.0.53", - "@ai-sdk/provider": "^1.1.3", - "@ai-sdk/provider-utils": "^2.2.8", - "@ai-sdk/provider-utils-v5": "npm:@ai-sdk/provider-utils@3.0.12", - "@ai-sdk/provider-v5": "npm:@ai-sdk/provider@2.0.0", - "@ai-sdk/ui-utils": "^1.2.11", - "@ai-sdk/xai-v5": "npm:@ai-sdk/xai@2.0.26", - "@isaacs/ttlcache": "^1.4.1", - "@mastra/schema-compat": "0.11.7", - "@openrouter/ai-sdk-provider-v5": "npm:@openrouter/ai-sdk-provider@1.2.0", - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/auto-instrumentations-node": "^0.62.1", - "@opentelemetry/core": "^2.0.1", - "@opentelemetry/exporter-trace-otlp-grpc": "^0.203.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.203.0", - "@opentelemetry/otlp-exporter-base": "^0.203.0", - "@opentelemetry/otlp-transformer": "^0.203.0", - "@opentelemetry/resources": "^2.0.1", - "@opentelemetry/sdk-metrics": "^2.0.1", - "@opentelemetry/sdk-node": "^0.203.0", - "@opentelemetry/sdk-trace-base": "^2.0.1", - "@opentelemetry/sdk-trace-node": "^2.0.1", - "@opentelemetry/semantic-conventions": "^1.36.0", - "@sindresorhus/slugify": "^2.2.1", - "ai": "^4.3.19", - "ai-v5": "npm:ai@5.0.76", - "date-fns": "^3.6.0", - "dotenv": "^16.6.1", - "hono": "^4.9.7", - "hono-openapi": "^0.4.8", - "js-tiktoken": "^1.0.20", - "json-schema": "^0.4.0", - "json-schema-to-zod": "^2.6.1", - "p-map": "^7.0.3", - "p-retry": "^7.1.0", - "pino": "^9.7.0", - "pino-pretty": "^13.0.0", - "radash": "^12.1.1", - "sift": "^17.1.3", - "xstate": "^5.20.1", - "zod-to-json-schema": "^3.24.6" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/@mastra/core/node_modules/@ai-sdk/provider-utils": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", - "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "1.1.3", - "nanoid": "^3.3.8", - "secure-json-parse": "^2.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.23.8" - } - }, - "node_modules/@mastra/core/node_modules/@ai-sdk/ui-utils": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", - "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.23.8" - } - }, - "node_modules/@mastra/core/node_modules/@mastra/schema-compat": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@mastra/schema-compat/-/schema-compat-0.11.7.tgz", - "integrity": "sha512-F/Q2myO7ar5+p2zRb1anGvLrUCKmdrJraXq9TWsWRp7frrcGUwlewf6Qo9+lp6NSumH0XlC/yvcjzzDwVzaMEw==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0", - "zod-from-json-schema": "^0.5.0", - "zod-from-json-schema-v3": "npm:zod-from-json-schema@^0.0.5", - "zod-to-json-schema": "^3.24.6" - }, - "peerDependencies": { - "ai": "^4.0.0 || ^5.0.0", - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/@mastra/core/node_modules/@openrouter/ai-sdk-provider-v5": { - "name": "@openrouter/ai-sdk-provider", - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-1.2.0.tgz", - "integrity": "sha512-stuIwq7Yb7DNmk3GuCtz+oS3nZOY4TXEV3V5KsknDGQN7Fpu3KRMQVWRc1J073xKdf0FC9EHOctSyzsACmp5Ag==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "ai": "^5.0.0", - "zod": "^3.24.1 || ^v4" - } - }, - "node_modules/@mastra/core/node_modules/ai": { - "version": "4.3.19", - "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.19.tgz", - "integrity": "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.8", - "@ai-sdk/react": "1.2.12", - "@ai-sdk/ui-utils": "1.2.11", - "@opentelemetry/api": "1.9.0", - "jsondiffpatch": "0.6.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } - } - }, - "node_modules/@mastra/core/node_modules/ai/node_modules/@ai-sdk/react": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz", - "integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider-utils": "2.2.8", - "@ai-sdk/ui-utils": "1.2.11", - "swr": "^2.2.5", - "throttleit": "2.1.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@mastra/core/node_modules/hono-openapi": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/hono-openapi/-/hono-openapi-0.4.8.tgz", - "integrity": "sha512-LYr5xdtD49M7hEAduV1PftOMzuT8ZNvkyWfh1DThkLsIr4RkvDb12UxgIiFbwrJB6FLtFXLoOZL9x4IeDk2+VA==", - "license": "MIT", - "dependencies": { - "json-schema-walker": "^2.0.0" - }, - "peerDependencies": { - "@hono/arktype-validator": "^2.0.0", - "@hono/effect-validator": "^1.2.0", - "@hono/typebox-validator": "^0.2.0 || ^0.3.0", - "@hono/valibot-validator": "^0.5.1", - "@hono/zod-validator": "^0.4.1", - "@sinclair/typebox": "^0.34.9", - "@valibot/to-json-schema": "^1.0.0-beta.3", - "arktype": "^2.0.0", - "effect": "^3.11.3", - "hono": "^4.6.13", - "openapi-types": "^12.1.3", - "valibot": "^1.0.0-beta.9", - "zod": "^3.23.8", - "zod-openapi": "^4.0.0" - }, - "peerDependenciesMeta": { - "@hono/arktype-validator": { - "optional": true - }, - "@hono/effect-validator": { - "optional": true - }, - "@hono/typebox-validator": { - "optional": true - }, - "@hono/valibot-validator": { - "optional": true - }, - "@hono/zod-validator": { - "optional": true - }, - "@sinclair/typebox": { - "optional": true - }, - "@valibot/to-json-schema": { - "optional": true - }, - "arktype": { - "optional": true - }, - "effect": { - "optional": true - }, - "hono": { - "optional": true - }, - "valibot": { - "optional": true - }, - "zod": { - "optional": true - }, - "zod-openapi": { - "optional": true - } - } - }, - "node_modules/@mastra/core/node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", - "license": "BSD-3-Clause" - }, - "node_modules/@mastra/core/node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, - "node_modules/@mastra/deployer": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@mastra/deployer/-/deployer-0.24.0.tgz", - "integrity": "sha512-nC5thq5zuPTtR59LKN61iFq+MFxiFJRDE2gWP6VbaMK7cYV2HAJriX1CRPpxqHNBGpS68V9Vk943YaMOcxIOXA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/core": "^7.28.4", - "@babel/helper-module-imports": "^7.27.1", - "@babel/preset-typescript": "^7.27.1", - "@mastra/server": "^0.24.0", - "@neon-rs/load": "^0.1.82", - "@optimize-lodash/rollup-plugin": "^5.0.2", - "@rollup/plugin-alias": "5.1.1", - "@rollup/plugin-commonjs": "28.0.6", - "@rollup/plugin-esm-shim": "0.1.8", - "@rollup/plugin-json": "6.1.0", - "@rollup/plugin-node-resolve": "16.0.2", - "@rollup/plugin-virtual": "3.0.2", - "@sindresorhus/slugify": "^2.2.1", - "builtins": "^5.1.0", - "detect-libc": "^2.0.4", - "dotenv": "^16.6.1", - "empathic": "^2.0.0", - "esbuild": "^0.25.10", - "find-workspaces": "^0.3.1", - "fs-extra": "^11.3.2", - "hono": "^4.9.7", - "local-pkg": "^1.1.2", - "resolve-from": "^5.0.0", - "resolve.exports": "^2.0.3", - "rollup": "~4.50.2", - "rollup-plugin-esbuild": "^6.2.1", - "rollup-plugin-node-externals": "^8.0.1", - "tinyglobby": "^0.2.15", - "typescript-paths": "^1.5.1" - }, - "peerDependencies": { - "@mastra/core": ">=0.24.0-0 <0.25.0-0", - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/@mastra/evals": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/@mastra/evals/-/evals-0.14.3.tgz", - "integrity": "sha512-LH0dyF3xOnTXynXWiJs7QKpS0oRrWXmBJ/DuAjCwl6zNYvfpYIyPM6mR7v19JRQPMpEpgY5WFiaws+XnRE3pGw==", - "license": "Apache-2.0", - "dependencies": { - "compromise": "^14.14.4", - "difflib": "^0.2.4", - "fs-extra": "^11.3.2", - "keyword-extractor": "^0.0.28", - "sentiment": "^5.0.2", - "string-similarity": "^4.0.4" - }, - "peerDependencies": { - "@mastra/core": ">=0.21.0-0 <0.25.0-0", - "ai": "^4.0.0 || ^5.0.0", - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/@mastra/libsql": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@mastra/libsql/-/libsql-0.16.2.tgz", - "integrity": "sha512-yQ3FYQkRdmIE9Z0RJqVJsKH7H4YjgYb4WcpTfe1C7ulz6OS0VANyXUxiggUhs/mC/WCRjXYlMIj5D19+8o+KeA==", - "license": "Apache-2.0", - "dependencies": { - "@libsql/client": "^0.15.15" - }, - "peerDependencies": { - "@mastra/core": ">=0.21.2-0 <0.25.0-0" - } - }, - "node_modules/@mastra/loggers": { - "version": "0.10.19", - "resolved": "https://registry.npmjs.org/@mastra/loggers/-/loggers-0.10.19.tgz", - "integrity": "sha512-H4qTgqKt3YJxz6B1I0lOPqRpOpR8gEzYYNZ6iO/TGQ11r+wvug/i80MAKr0B1IBZvr9l18vr7wjYCdTxPqaAGg==", - "license": "Apache-2.0", - "dependencies": { - "pino": "^9.7.0", - "pino-pretty": "^13.0.0" - }, - "peerDependencies": { - "@mastra/core": ">=0.18.1-0 <0.25.0-0" - } - }, - "node_modules/@mastra/memory": { - "version": "0.15.11", - "resolved": "https://registry.npmjs.org/@mastra/memory/-/memory-0.15.11.tgz", - "integrity": "sha512-HxcvJ+JBCEN9f8JQ+mPgxBm2K9Ln52aGsX0OeqG1sgt1vac60PqQi1MbywbiIsLxlMkVFysE2C/7bFh+d6vtjQ==", - "license": "Apache-2.0", - "dependencies": { - "@mastra/schema-compat": "0.11.7", - "@upstash/redis": "^1.35.5", - "ai": "^4.3.19", - "ai-v5": "npm:ai@5.0.60", - "async-mutex": "^0.5.0", - "js-tiktoken": "^1.0.20", - "json-schema": "^0.4.0", - "pg": "^8.16.3", - "pg-pool": "^3.10.1", - "postgres": "^3.4.7", - "redis": "^5.8.3", - "xxhash-wasm": "^1.1.0", - "zod-to-json-schema": "^3.24.6" - }, - "peerDependencies": { - "@mastra/core": ">=0.20.1-0 <0.25.0-0", - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/@mastra/memory/node_modules/@ai-sdk/gateway": { - "version": "1.0.33", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-1.0.33.tgz", - "integrity": "sha512-v9i3GPEo4t3fGcSkQkc07xM6KJN75VUv7C1Mqmmsu2xD8lQwnQfsrgAXyNuWe20yGY0eHuheSPDZhiqsGKtH1g==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.10", - "@vercel/oidc": "^3.0.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@mastra/memory/node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@mastra/memory/node_modules/@ai-sdk/provider-utils": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.10.tgz", - "integrity": "sha512-T1gZ76gEIwffep6MWI0QNy9jgoybUHE7TRaHB5k54K8mF91ciGFlbtCGxDYhMH3nCRergKwYFIDeFF0hJSIQHQ==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@mastra/memory/node_modules/@ai-sdk/provider-utils/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@mastra/memory/node_modules/@mastra/schema-compat": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@mastra/schema-compat/-/schema-compat-0.11.7.tgz", - "integrity": "sha512-F/Q2myO7ar5+p2zRb1anGvLrUCKmdrJraXq9TWsWRp7frrcGUwlewf6Qo9+lp6NSumH0XlC/yvcjzzDwVzaMEw==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0", - "zod-from-json-schema": "^0.5.0", - "zod-from-json-schema-v3": "npm:zod-from-json-schema@^0.0.5", - "zod-to-json-schema": "^3.24.6" - }, - "peerDependencies": { - "ai": "^4.0.0 || ^5.0.0", - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/@mastra/memory/node_modules/ai": { - "version": "4.3.19", - "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.19.tgz", - "integrity": "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.8", - "@ai-sdk/react": "1.2.12", - "@ai-sdk/ui-utils": "1.2.11", - "@opentelemetry/api": "1.9.0", - "jsondiffpatch": "0.6.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } - } - }, - "node_modules/@mastra/memory/node_modules/ai-v5": { - "name": "ai", - "version": "5.0.60", - "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.60.tgz", - "integrity": "sha512-80U/3kmdBW6g+JkLXpz/P2EwkyEaWlPlYtuLUpx/JYK9F7WZh9NnkYoh1KvUi1Sbpo0NyurBTvX0a2AG9mmbDA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/gateway": "1.0.33", - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.10", - "@opentelemetry/api": "1.9.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@mastra/memory/node_modules/ai-v5/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@mastra/memory/node_modules/ai/node_modules/@ai-sdk/provider-utils": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", - "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "1.1.3", - "nanoid": "^3.3.8", - "secure-json-parse": "^2.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.23.8" - } - }, - "node_modules/@mastra/memory/node_modules/ai/node_modules/@ai-sdk/react": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz", - "integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider-utils": "2.2.8", - "@ai-sdk/ui-utils": "1.2.11", - "swr": "^2.2.5", - "throttleit": "2.1.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@mastra/memory/node_modules/ai/node_modules/@ai-sdk/ui-utils": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", - "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.23.8" - } - }, - "node_modules/@mastra/memory/node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", - "license": "BSD-3-Clause" - }, - "node_modules/@mastra/memory/node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, - "node_modules/@mastra/server": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@mastra/server/-/server-0.24.0.tgz", - "integrity": "sha512-/p26002D8Lpg4bztazSJZxVtoAmi7cwHLkhgEdFf352ePxn44JOLLdBq/3Jk2EJ0xjfPwsDs8ByqpKBOOrybYw==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "@mastra/core": ">=0.24.0-0 <0.25.0-0", - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/@neon-rs/load": { - "version": "0.1.82", - "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.1.82.tgz", - "integrity": "sha512-H4Gu2o5kPp+JOEhRrOQCnJnf7X6sv9FBLttM/wSbb4efsgFWeHzfU/ItZ01E5qqEk+U6QGdeVO7lxXIAtYHr5A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/api-logs": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.203.0.tgz", - "integrity": "sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node": { - "version": "0.62.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.62.2.tgz", - "integrity": "sha512-Ipe6X7ddrCiRsuewyTU83IvKiSFT4piqmv9z8Ovg1E7v98pdTj1pUE6sDrHV50zl7/ypd+cONBgt+EYSZu4u9Q==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/instrumentation-amqplib": "^0.50.0", - "@opentelemetry/instrumentation-aws-lambda": "^0.54.1", - "@opentelemetry/instrumentation-aws-sdk": "^0.58.0", - "@opentelemetry/instrumentation-bunyan": "^0.49.0", - "@opentelemetry/instrumentation-cassandra-driver": "^0.49.0", - "@opentelemetry/instrumentation-connect": "^0.47.0", - "@opentelemetry/instrumentation-cucumber": "^0.19.0", - "@opentelemetry/instrumentation-dataloader": "^0.21.1", - "@opentelemetry/instrumentation-dns": "^0.47.0", - "@opentelemetry/instrumentation-express": "^0.52.0", - "@opentelemetry/instrumentation-fastify": "^0.48.0", - "@opentelemetry/instrumentation-fs": "^0.23.0", - "@opentelemetry/instrumentation-generic-pool": "^0.47.0", - "@opentelemetry/instrumentation-graphql": "^0.51.0", - "@opentelemetry/instrumentation-grpc": "^0.203.0", - "@opentelemetry/instrumentation-hapi": "^0.50.0", - "@opentelemetry/instrumentation-http": "^0.203.0", - "@opentelemetry/instrumentation-ioredis": "^0.51.0", - "@opentelemetry/instrumentation-kafkajs": "^0.13.0", - "@opentelemetry/instrumentation-knex": "^0.48.0", - "@opentelemetry/instrumentation-koa": "^0.51.0", - "@opentelemetry/instrumentation-lru-memoizer": "^0.48.0", - "@opentelemetry/instrumentation-memcached": "^0.47.0", - "@opentelemetry/instrumentation-mongodb": "^0.56.0", - "@opentelemetry/instrumentation-mongoose": "^0.50.0", - "@opentelemetry/instrumentation-mysql": "^0.49.0", - "@opentelemetry/instrumentation-mysql2": "^0.50.0", - "@opentelemetry/instrumentation-nestjs-core": "^0.49.0", - "@opentelemetry/instrumentation-net": "^0.47.0", - "@opentelemetry/instrumentation-oracledb": "^0.29.0", - "@opentelemetry/instrumentation-pg": "^0.56.1", - "@opentelemetry/instrumentation-pino": "^0.50.1", - "@opentelemetry/instrumentation-redis": "^0.52.0", - "@opentelemetry/instrumentation-restify": "^0.49.0", - "@opentelemetry/instrumentation-router": "^0.48.0", - "@opentelemetry/instrumentation-runtime-node": "^0.17.1", - "@opentelemetry/instrumentation-socket.io": "^0.50.0", - "@opentelemetry/instrumentation-tedious": "^0.22.0", - "@opentelemetry/instrumentation-undici": "^0.14.0", - "@opentelemetry/instrumentation-winston": "^0.48.1", - "@opentelemetry/resource-detector-alibaba-cloud": "^0.31.3", - "@opentelemetry/resource-detector-aws": "^2.3.0", - "@opentelemetry/resource-detector-azure": "^0.10.0", - "@opentelemetry/resource-detector-container": "^0.7.3", - "@opentelemetry/resource-detector-gcp": "^0.37.0", - "@opentelemetry/resources": "^2.0.0", - "@opentelemetry/sdk-node": "^0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.4.1", - "@opentelemetry/core": "^2.0.0" - } - }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.2.0.tgz", - "integrity": "sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ==", - "license": "Apache-2.0", - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", - "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.203.0.tgz", - "integrity": "sha512-g/2Y2noc/l96zmM+g0LdeuyYKINyBwN6FJySoU15LHPLcMN/1a0wNk2SegwKcxrRdE7Xsm7fkIR5n6XFe3QpPw==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/otlp-exporter-base": "0.203.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.203.0", - "@opentelemetry/otlp-transformer": "0.203.0", - "@opentelemetry/sdk-logs": "0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-http": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.203.0.tgz", - "integrity": "sha512-s0hys1ljqlMTbXx2XiplmMJg9wG570Z5lH7wMvrZX6lcODI56sG4HL03jklF63tBeyNwK2RV1/ntXGo3HgG4Qw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.203.0", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/otlp-exporter-base": "0.203.0", - "@opentelemetry/otlp-transformer": "0.203.0", - "@opentelemetry/sdk-logs": "0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.203.0.tgz", - "integrity": "sha512-nl/7S91MXn5R1aIzoWtMKGvqxgJgepB/sH9qW0rZvZtabnsjbf8OQ1uSx3yogtvLr0GzwD596nQKz2fV7q2RBw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.203.0", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/otlp-exporter-base": "0.203.0", - "@opentelemetry/otlp-transformer": "0.203.0", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-logs": "0.203.0", - "@opentelemetry/sdk-trace-base": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", - "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", - "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.203.0.tgz", - "integrity": "sha512-FCCj9nVZpumPQSEI57jRAA89hQQgONuoC35Lt+rayWY/mzCAc6BQT7RFyFaZKJ2B7IQ8kYjOCPsF/HGFWjdQkQ==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", - "@opentelemetry/otlp-exporter-base": "0.203.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.203.0", - "@opentelemetry/otlp-transformer": "0.203.0", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-metrics": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", - "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", - "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.203.0.tgz", - "integrity": "sha512-HFSW10y8lY6BTZecGNpV3GpoSy7eaO0Z6GATwZasnT4bEsILp8UJXNG5OmEsz4SdwCSYvyCbTJdNbZP3/8LGCQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/otlp-exporter-base": "0.203.0", - "@opentelemetry/otlp-transformer": "0.203.0", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-metrics": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", - "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", - "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.203.0.tgz", - "integrity": "sha512-OZnhyd9npU7QbyuHXFEPVm3LnjZYifuKpT3kTnF84mXeEQ84pJJZgyLBpU4FSkSwUkt/zbMyNAI7y5+jYTWGIg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", - "@opentelemetry/otlp-exporter-base": "0.203.0", - "@opentelemetry/otlp-transformer": "0.203.0", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-metrics": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", - "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", - "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-prometheus": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.203.0.tgz", - "integrity": "sha512-2jLuNuw5m4sUj/SncDf/mFPabUxMZmmYetx5RKIMIQyPnl6G6ooFzfeE8aXNRf8YD1ZXNlCnRPcISxjveGJHNg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-metrics": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", - "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", - "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.203.0.tgz", - "integrity": "sha512-322coOTf81bm6cAA8+ML6A+m4r2xTCdmAZzGNTboPXRzhwPt4JEmovsFAs+grpdarObd68msOJ9FfH3jxM6wqA==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/otlp-exporter-base": "0.203.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.203.0", - "@opentelemetry/otlp-transformer": "0.203.0", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-trace-base": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", - "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", - "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.203.0.tgz", - "integrity": "sha512-ZDiaswNYo0yq/cy1bBLJFe691izEJ6IgNmkjm4C6kE9ub/OMQqDXORx2D2j8fzTBTxONyzusbaZlqtfmyqURPw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/otlp-exporter-base": "0.203.0", - "@opentelemetry/otlp-transformer": "0.203.0", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-trace-base": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", - "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", - "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.203.0.tgz", - "integrity": "sha512-1xwNTJ86L0aJmWRwENCJlH4LULMG2sOXWIVw+Szta4fkqKVY50Eo4HoVKKq6U9QEytrWCr8+zjw0q/ZOeXpcAQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/otlp-exporter-base": "0.203.0", - "@opentelemetry/otlp-transformer": "0.203.0", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-trace-base": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", - "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", - "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-zipkin": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.0.1.tgz", - "integrity": "sha512-a9eeyHIipfdxzCfc2XPrE+/TI3wmrZUDFtG2RRXHSbZZULAny7SyybSvaDvS77a7iib5MPiAvluwVvbGTsHxsw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-trace-base": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", - "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", - "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/instrumentation": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.203.0.tgz", - "integrity": "sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.203.0", - "import-in-the-middle": "^1.8.1", - "require-in-the-middle": "^7.1.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-amqplib": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.50.0.tgz", - "integrity": "sha512-kwNs/itehHG/qaQBcVrLNcvXVPW0I4FCOVtw3LHMLdYIqD7GJ6Yv2nX+a4YHjzbzIeRYj8iyMp0Bl7tlkidq5w==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-aws-lambda": { - "version": "0.54.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.54.1.tgz", - "integrity": "sha512-qm8pGSAM1mXk7unbrGktWWGJc6IFI58ZsaHJ+i420Fp5VO3Vf7GglIgaXTS8CKBrVB4LHFj3NvzJg31PtsAQcA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/aws-lambda": "8.10.152" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-aws-sdk": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.58.0.tgz", - "integrity": "sha512-9vFH7gU686dsAeLMCkqUj9y0MQZ1xrTtStSpNV2UaGWtDnRjJrAdJLu9Y545oKEaDTeVaob4UflyZvvpZnw3Xw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.34.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-bunyan": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.49.0.tgz", - "integrity": "sha512-ky5Am1y6s3Ex/3RygHxB/ZXNG07zPfg9Z6Ora+vfeKcr/+I6CJbWXWhSBJor3gFgKN3RvC11UWVURnmDpBS6Pg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "^0.203.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@types/bunyan": "1.8.11" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-cassandra-driver": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.49.0.tgz", - "integrity": "sha512-BNIvqldmLkeikfI5w5Rlm9vG5NnQexfPoxOgEMzfDVOEF+vS6351I6DzWLLgWWR9CNF/jQJJi/lr6am2DLp0Rw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-connect": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.47.0.tgz", - "integrity": "sha512-pjenvjR6+PMRb6/4X85L4OtkQCootgb/Jzh/l/Utu3SJHBid1F+gk9sTGU2FWuhhEfV6P7MZ7BmCdHXQjgJ42g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/connect": "3.4.38" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-cucumber": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.19.0.tgz", - "integrity": "sha512-99ms8kQWRuPt5lkDqbJJzD+7Tq5TMUlBZki4SA2h6CgK4ncX+tyep9XFY1e+XTBLJIWmuFMGbWqBLJ4fSKIQNQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/instrumentation-dataloader": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.21.1.tgz", - "integrity": "sha512-hNAm/bwGawLM8VDjKR0ZUDJ/D/qKR3s6lA5NV+btNaPVm2acqhPcT47l2uCVi+70lng2mywfQncor9v8/ykuyw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-dns": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.47.0.tgz", - "integrity": "sha512-775fOnewWkTF4iXMGKgwvOGqEmPrU1PZpXjjqvTrEErYBJe7Fz1WlEeUStHepyKOdld7Ghv7TOF/kE3QDctvrg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-express": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.52.0.tgz", - "integrity": "sha512-W7pizN0Wh1/cbNhhTf7C62NpyYw7VfCFTYg0DYieSTrtPBT1vmoSZei19wfKLnrMsz3sHayCg0HxCVL2c+cz5w==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-fastify": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.48.0.tgz", - "integrity": "sha512-3zQlE/DoVfVH6/ycuTv7vtR/xib6WOa0aLFfslYcvE62z0htRu/ot8PV/zmMZfnzpTQj8S/4ULv36R6UIbpJIg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-fs": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.23.0.tgz", - "integrity": "sha512-Puan+QopWHA/KNYvDfOZN6M/JtF6buXEyD934vrb8WhsX1/FuM7OtoMlQyIqAadnE8FqqDL4KDPiEfCQH6pQcQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-generic-pool": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.47.0.tgz", - "integrity": "sha512-UfHqf3zYK+CwDwEtTjaD12uUqGGTswZ7ofLBEdQ4sEJp9GHSSJMQ2hT3pgBxyKADzUdoxQAv/7NqvL42ZI+Qbw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-graphql": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.51.0.tgz", - "integrity": "sha512-LchkOu9X5DrXAnPI1+Z06h/EH/zC7D6sA86hhPrk3evLlsJTz0grPrkL/yUJM9Ty0CL/y2HSvmWQCjbJEz/ADg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-grpc": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.203.0.tgz", - "integrity": "sha512-Qmjx2iwccHYRLoE4RFS46CvQE9JG9Pfeae4EPaNZjvIuJxb/pZa2R9VWzRlTehqQWpAvto/dGhtkw8Tv+o0LTg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "0.203.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-hapi": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.50.0.tgz", - "integrity": "sha512-5xGusXOFQXKacrZmDbpHQzqYD1gIkrMWuwvlrEPkYOsjUqGUjl1HbxCsn5Y9bUXOCgP1Lj6A4PcKt1UiJ2MujA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.203.0.tgz", - "integrity": "sha512-y3uQAcCOAwnO6vEuNVocmpVzG3PER6/YZqbPbbffDdJ9te5NkHEkfSMNzlC3+v7KlE+WinPGc3N7MR30G1HY2g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/instrumentation": "0.203.0", - "@opentelemetry/semantic-conventions": "^1.29.0", - "forwarded-parse": "2.1.2" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/instrumentation-ioredis": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.51.0.tgz", - "integrity": "sha512-9IUws0XWCb80NovS+17eONXsw1ZJbHwYYMXiwsfR9TSurkLV5UNbRSKb9URHO+K+pIJILy9wCxvyiOneMr91Ig==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/redis-common": "^0.38.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-kafkajs": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.13.0.tgz", - "integrity": "sha512-FPQyJsREOaGH64hcxlzTsIEQC4DYANgTwHjiB7z9lldmvua1LRMVn3/FfBlzXoqF179B0VGYviz6rn75E9wsDw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.30.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-knex": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.48.0.tgz", - "integrity": "sha512-V5wuaBPv/lwGxuHjC6Na2JFRjtPgstw19jTFl1B1b6zvaX8zVDYUDaR5hL7glnQtUSCMktPttQsgK4dhXpddcA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.33.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-koa": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.51.0.tgz", - "integrity": "sha512-XNLWeMTMG1/EkQBbgPYzCeBD0cwOrfnn8ao4hWgLv0fNCFQu1kCsJYygz2cvKuCs340RlnG4i321hX7R8gj3Rg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.48.0.tgz", - "integrity": "sha512-KUW29wfMlTPX1wFz+NNrmE7IzN7NWZDrmFWHM/VJcmFEuQGnnBuTIdsP55CnBDxKgQ/qqYFp4udQFNtjeFosPw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-memcached": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.47.0.tgz", - "integrity": "sha512-vXDs/l4hlWy1IepPG1S6aYiIZn+tZDI24kAzwKKJmR2QEJRL84PojmALAEJGazIOLl/VdcCPZdMb0U2K0VzojA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/memcached": "^2.2.6" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.56.0.tgz", - "integrity": "sha512-YG5IXUUmxX3Md2buVMvxm9NWlKADrnavI36hbJsihqqvBGsWnIfguf0rUP5Srr0pfPqhQjUP+agLMsvu0GmUpA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-mongoose": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.50.0.tgz", - "integrity": "sha512-Am8pk1Ct951r4qCiqkBcGmPIgGhoDiFcRtqPSLbJrUZqEPUsigjtMjoWDRLG1Ki1NHgOF7D0H7d+suWz1AAizw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-mysql": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.49.0.tgz", - "integrity": "sha512-QU9IUNqNsrlfE3dJkZnFHqLjlndiU39ll/YAAEvWE40sGOCi9AtOF6rmEGzJ1IswoZ3oyePV7q2MP8SrhJfVAA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/mysql": "2.15.27" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.50.0.tgz", - "integrity": "sha512-PoOMpmq73rOIE3nlTNLf3B1SyNYGsp7QXHYKmeTZZnJ2Ou7/fdURuOhWOI0e6QZ5gSem18IR1sJi6GOULBQJ9g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@opentelemetry/sql-common": "^0.41.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-nestjs-core": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.49.0.tgz", - "integrity": "sha512-1R/JFwdmZIk3T/cPOCkVvFQeKYzbbUvDxVH3ShXamUwBlGkdEu5QJitlRMyVNZaHkKZKWgYrBarGQsqcboYgaw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.30.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-net": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.47.0.tgz", - "integrity": "sha512-csoJ++Njpf7C09JH+0HNGenuNbDZBqO1rFhMRo6s0rAmJwNh9zY3M/urzptmKlqbKnf4eH0s+CKHy/+M8fbFsQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-oracledb": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-oracledb/-/instrumentation-oracledb-0.29.0.tgz", - "integrity": "sha512-2aHLiJdkyiUbooIUm7FaZf+O4jyqEl+RfFpgud1dxT87QeeYM216wi+xaMNzsb5yKtRBqbA3qeHBCyenYrOZwA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/oracledb": "6.5.2" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.56.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.56.1.tgz", - "integrity": "sha512-0/PiHDPVaLdcXNw6Gqb3JBdMxComMEwh444X8glwiynJKJHRTR49+l2cqJfoOVzB8Sl1XRl3Yaqw6aDi3s8e9w==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.34.0", - "@opentelemetry/sql-common": "^0.41.0", - "@types/pg": "8.15.5", - "@types/pg-pool": "2.0.6" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-pino": { - "version": "0.50.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.50.1.tgz", - "integrity": "sha512-pBbvuWiHA9iAumAuQ0SKYOXK7NRlbnVTf/qBV0nMdRnxBPrc/GZTbh0f7Y59gZfYsbCLhXLL1oRTEnS+PwS3CA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "^0.203.0", - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-redis": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.52.0.tgz", - "integrity": "sha512-R8Y7cCZlJ2Vl31S2i7bl5SqyC/aul54ski4wCFip/Tp9WGtLK1xVATi2rwy2wkc8ZCtjdEe9eEVR+QFG6gGZxg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/redis-common": "^0.38.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-restify": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.49.0.tgz", - "integrity": "sha512-tsGZZhS4mVZH7omYxw5jpsrD3LhWizqWc0PYtAnzpFUvL5ZINHE+cm57bssTQ2AK/GtZMxu9LktwCvIIf3dSmw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-router": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.48.0.tgz", - "integrity": "sha512-Wixrc8CchuJojXpaS/dCQjFOMc+3OEil1H21G+WLYQb8PcKt5kzW9zDBT19nyjjQOx/D/uHPfgbrT+Dc7cfJ9w==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-runtime-node": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-runtime-node/-/instrumentation-runtime-node-0.17.1.tgz", - "integrity": "sha512-c1FlAk+bB2uF9a8YneGmNPTl7c/xVaan4mmWvbkWcOmH/ipKqR1LaKUlz/BMzLrJLjho1EJlG2NrS2w2Arg+nw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-socket.io": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.50.0.tgz", - "integrity": "sha512-6JN6lnKN9ZuZtZdMQIR+no1qHzQvXSZUsNe3sSWMgqmNRyEXuDUWBIyKKeG0oHRHtR4xE4QhJyD4D5kKRPWZFA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-tedious": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.22.0.tgz", - "integrity": "sha512-XrrNSUCyEjH1ax9t+Uo6lv0S2FCCykcF7hSxBMxKf7Xn0bPRxD3KyFUZy25aQXzbbbUHhtdxj3r2h88SfEM3aA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/tedious": "^4.0.14" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-undici": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.14.0.tgz", - "integrity": "sha512-2HN+7ztxAReXuxzrtA3WboAKlfP5OsPA57KQn2AdYZbJ3zeRPcLXyW4uO/jpLE6PLm0QRtmeGCmfYpqRlwgSwg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.7.0" - } - }, - "node_modules/@opentelemetry/instrumentation-winston": { - "version": "0.48.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.48.1.tgz", - "integrity": "sha512-XyOuVwdziirHHYlsw+BWrvdI/ymjwnexupKA787zQQ+D5upaE/tseZxjfQa7+t4+FdVLxHICaMTmkSD4yZHpzQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "^0.203.0", - "@opentelemetry/instrumentation": "^0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.203.0.tgz", - "integrity": "sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/otlp-transformer": "0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.203.0.tgz", - "integrity": "sha512-te0Ze1ueJF+N/UOFl5jElJW4U0pZXQ8QklgSfJ2linHN0JJsuaHG8IabEUi2iqxY8ZBDlSiz1Trfv5JcjWWWwQ==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/otlp-exporter-base": "0.203.0", - "@opentelemetry/otlp-transformer": "0.203.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.203.0.tgz", - "integrity": "sha512-Y8I6GgoCna0qDQ2W6GCRtaF24SnvqvA8OfeTi7fqigD23u8Jpb4R5KFv/pRvrlGagcCLICMIyh9wiejp4TXu/A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.203.0", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-logs": "0.203.0", - "@opentelemetry/sdk-metrics": "2.0.1", - "@opentelemetry/sdk-trace-base": "2.0.1", - "protobufjs": "^7.3.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", - "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", - "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", - "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-b3": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.0.1.tgz", - "integrity": "sha512-Hc09CaQ8Tf5AGLmf449H726uRoBNGPBL4bjr7AnnUpzWMvhdn61F78z9qb6IqB737TffBsokGAK1XykFEZ1igw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-jaeger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.0.1.tgz", - "integrity": "sha512-7PMdPBmGVH2eQNb/AtSJizQNgeNTfh6jQFqys6lfhd6P4r+m/nTh3gKPPpaCXVdRQ+z93vfKk+4UGty390283w==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/redis-common": { - "version": "0.38.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz", - "integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==", - "license": "Apache-2.0", - "engines": { - "node": "^18.19.0 || >=20.6.0" - } - }, - "node_modules/@opentelemetry/resource-detector-alibaba-cloud": { - "version": "0.31.11", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.31.11.tgz", - "integrity": "sha512-R/asn6dAOWMfkLeEwqHCUz0cNbb9oiHVyd11iwlypeT/p9bR1lCX5juu5g/trOwxo62dbuFcDbBdKCJd3O2Edg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/resources": "^2.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/resource-detector-aws": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-2.8.0.tgz", - "integrity": "sha512-L8K5L3bsDKboX7sDofZyRonyK8dfS+CF7ho8YbZ6OrH+d5uyRBsrjuokPzcju1jP2ZzgtpYzhLwzi9zPXyRLlA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/resources": "^2.0.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/resource-detector-azure": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.10.0.tgz", - "integrity": "sha512-5cNAiyPBg53Uxe/CW7hsCq8HiKNAUGH+gi65TtgpzSR9bhJG4AEbuZhbJDFwe97tn2ifAD1JTkbc/OFuaaFWbA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/resources": "^2.0.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/resource-detector-container": { - "version": "0.7.11", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.7.11.tgz", - "integrity": "sha512-XUxnGuANa/EdxagipWMXKYFC7KURwed9/V0+NtYjFmwWHzV9/J4IYVGTK8cWDpyUvAQf/vE4sMa3rnS025ivXQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/resources": "^2.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/resource-detector-gcp": { - "version": "0.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.37.0.tgz", - "integrity": "sha512-LGpJBECIMsVKhiulb4nxUw++m1oF4EiDDPmFGW2aqYaAF0oUvJNv8Z/55CAzcZ7SxvlTgUwzewXDBsuCup7iqw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/resources": "^2.0.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "gcp-metadata": "^6.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/resources": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", - "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.2.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-logs": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.203.0.tgz", - "integrity": "sha512-vM2+rPq0Vi3nYA5akQD2f3QwossDnTDLvKbea6u/A2NZ3XDkPxMfo/PNrDoXhDUD/0pPo2CdH5ce/thn9K0kLw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.203.0", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", - "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-metrics": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz", - "integrity": "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.2.0", - "@opentelemetry/resources": "2.2.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-node": { - "version": "0.203.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.203.0.tgz", - "integrity": "sha512-zRMvrZGhGVMvAbbjiNQW3eKzW/073dlrSiAKPVWmkoQzah9wfynpVPeL55f9fVIm0GaBxTLcPeukWGy0/Wj7KQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.203.0", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/exporter-logs-otlp-grpc": "0.203.0", - "@opentelemetry/exporter-logs-otlp-http": "0.203.0", - "@opentelemetry/exporter-logs-otlp-proto": "0.203.0", - "@opentelemetry/exporter-metrics-otlp-grpc": "0.203.0", - "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", - "@opentelemetry/exporter-metrics-otlp-proto": "0.203.0", - "@opentelemetry/exporter-prometheus": "0.203.0", - "@opentelemetry/exporter-trace-otlp-grpc": "0.203.0", - "@opentelemetry/exporter-trace-otlp-http": "0.203.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.203.0", - "@opentelemetry/exporter-zipkin": "2.0.1", - "@opentelemetry/instrumentation": "0.203.0", - "@opentelemetry/propagator-b3": "2.0.1", - "@opentelemetry/propagator-jaeger": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-logs": "0.203.0", - "@opentelemetry/sdk-metrics": "2.0.1", - "@opentelemetry/sdk-trace-base": "2.0.1", - "@opentelemetry/sdk-trace-node": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/context-async-hooks": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.0.1.tgz", - "integrity": "sha512-XuY23lSI3d4PEqKA+7SLtAgwqIfc6E/E9eAQWLN1vlpC53ybO3o6jW4BsXo1xvz9lYyyWItfQDDLzezER01mCw==", - "license": "Apache-2.0", - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", - "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", - "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", - "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", - "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-trace-node": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.0.1.tgz", - "integrity": "sha512-UhdbPF19pMpBtCWYP5lHbTogLWx9N0EBxtdagvkn5YtsAnCBZzL7SjktG+ZmupRgifsHMjwUaCCaVmqGfSADmA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/context-async-hooks": "2.0.1", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/sdk-trace-base": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", - "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.2.0", - "@opentelemetry/resources": "2.2.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-node": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.2.0.tgz", - "integrity": "sha512-+OaRja3f0IqGG2kptVeYsrZQK9nKRSpfFrKtRBq4uh6nIB8bTBgaGvYQrQoRrQWQMA5dK5yLhDMDc0dvYvCOIQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/context-async-hooks": "2.2.0", - "@opentelemetry/core": "2.2.0", - "@opentelemetry/sdk-trace-base": "2.2.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", - "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sql-common": { - "version": "0.41.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz", - "integrity": "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0" - } - }, - "node_modules/@optimize-lodash/rollup-plugin": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@optimize-lodash/rollup-plugin/-/rollup-plugin-5.0.2.tgz", - "integrity": "sha512-UWBD9/C5jO0rDAbiqrZqiTLPD0LOHG3DzBo8ubLTpNWY9xOz5f5+S2yuxG/7ICk8sx8K6pZ8O/jsAbFgjtfh6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@optimize-lodash/transform": "3.0.6", - "@rollup/pluginutils": "^5.1.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "rollup": ">= 4.x" - } - }, - "node_modules/@optimize-lodash/transform": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@optimize-lodash/transform/-/transform-3.0.6.tgz", - "integrity": "sha512-9+qMSaDpahC0+vX2ChM46/ls6a5Ankqs6RTLrHSaFpm7o1mFanP82e+jm9/0o5D660ueK8dWJGPCXQrBxBNNWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "estree-walker": "^2.0.2", - "magic-string": "~0.30.11" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "license": "MIT" - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" - }, - "node_modules/@redis/bloom": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.9.0.tgz", - "integrity": "sha512-W9D8yfKTWl4tP8lkC3MRYkMz4OfbuzE/W8iObe0jFgoRmgMfkBV+Vj38gvIqZPImtY0WB34YZkX3amYuQebvRQ==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.9.0" - } - }, - "node_modules/@redis/client": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.9.0.tgz", - "integrity": "sha512-EI0Ti5pojD2p7TmcS7RRa+AJVahdQvP/urpcSbK/K9Rlk6+dwMJTQ354pCNGCwfke8x4yKr5+iH85wcERSkwLQ==", - "license": "MIT", - "dependencies": { - "cluster-key-slot": "1.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@redis/json": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.9.0.tgz", - "integrity": "sha512-Bm2jjLYaXdUWPb9RaEywxnjmzw7dWKDZI4MS79mTWPV16R982jVWBj6lY2ZGelJbwxHtEVg4/FSVgYDkuO/MxA==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.9.0" - } - }, - "node_modules/@redis/search": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.9.0.tgz", - "integrity": "sha512-jdk2csmJ29DlpvCIb2ySjix2co14/0iwIT3C0I+7ZaToXgPbgBMB+zfEilSuncI2F9JcVxHki0YtLA0xX3VdpA==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.9.0" - } - }, - "node_modules/@redis/time-series": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.9.0.tgz", - "integrity": "sha512-W6ILxcyOqhnI7ELKjJXOktIg3w4+aBHugDbVpgVLPZ+YDjObis1M0v7ZzwlpXhlpwsfePfipeSK+KWNuymk52w==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.9.0" - } - }, - "node_modules/@rollup/plugin-alias": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz", - "integrity": "sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "28.0.6", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz", - "integrity": "sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "fdir": "^6.2.0", - "is-reference": "1.2.1", - "magic-string": "^0.30.3", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=16.0.0 || 14 >= 14.17" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-esm-shim": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@rollup/plugin-esm-shim/-/plugin-esm-shim-0.1.8.tgz", - "integrity": "sha512-xEU0b/BShgDDSPjidhJd4R74J9xZ9jLVtFWNGtsUXyEsdwwwB1a3XOAwwGaNIyUHD6EhxPO21JMfUmJWoMn7SA==", - "dev": true, - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.3", - "mlly": "^1.7.4" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-json": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", - "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.2.tgz", - "integrity": "sha512-tCtHJ2BlhSoK4cCs25NMXfV7EALKr0jyasmqVCq3y9cBrKdmJhtsy1iTz36Xhk/O+pDJbzawxF4K6ZblqCnITQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-virtual": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", - "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz", - "integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz", - "integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz", - "integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz", - "integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz", - "integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz", - "integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz", - "integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz", - "integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz", - "integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz", - "integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz", - "integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz", - "integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz", - "integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz", - "integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz", - "integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz", - "integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz", - "integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz", - "integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz", - "integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz", - "integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz", - "integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@sindresorhus/slugify": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", - "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", - "license": "MIT", - "dependencies": { - "@sindresorhus/transliterate": "^1.0.0", - "escape-string-regexp": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@sindresorhus/transliterate": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", - "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "license": "MIT" - }, - "node_modules/@types/aws-lambda": { - "version": "8.10.152", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.152.tgz", - "integrity": "sha512-soT/c2gYBnT5ygwiHPmd9a1bftj462NWVk2tKCc1PYHSIacB2UwbTS2zYG4jzag1mRDuzg/OjtxQjQ2NKRB6Rw==", - "license": "MIT" - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bunyan": { - "version": "1.8.11", - "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.11.tgz", - "integrity": "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cors": { - "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/diff-match-patch": { - "version": "1.0.36", - "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", - "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "4.17.25", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", - "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "^1" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", - "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" - }, - "node_modules/@types/memcached": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/@types/memcached/-/memcached-2.2.10.tgz", - "integrity": "sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "license": "MIT" - }, - "node_modules/@types/mysql": { - "version": "2.15.27", - "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", - "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "24.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", - "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/oracledb": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@types/oracledb/-/oracledb-6.5.2.tgz", - "integrity": "sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/pg": { - "version": "8.15.5", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.5.tgz", - "integrity": "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, - "node_modules/@types/pg-pool": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", - "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", - "license": "MIT", - "dependencies": { - "@types/pg": "*" - } - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "license": "MIT" - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/tedious": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", - "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@upstash/redis": { - "version": "1.35.6", - "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.35.6.tgz", - "integrity": "sha512-aSEIGJgJ7XUfTYvhQcQbq835re7e/BXjs8Janq6Pvr6LlmTZnyqwT97RziZLO/8AVUL037RLXqqiQC6kCt+5pA==", - "license": "MIT", - "dependencies": { - "uncrypto": "^0.1.3" - } - }, - "node_modules/@vercel/oidc": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.3.tgz", - "integrity": "sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg==", - "license": "Apache-2.0", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@webcontainer/env": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@webcontainer/env/-/env-1.1.1.tgz", - "integrity": "sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==", - "dev": true, - "license": "MIT" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/agentfs-sdk": { - "resolved": "../../../sdk/typescript", - "link": true - }, - "node_modules/ai": { - "version": "5.0.92", - "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.92.tgz", - "integrity": "sha512-EnPe3QXiD06Tg7iAt/oU3JSwedI1nuhEBnTjyfn1qTXaqmJ6qI4YG8wn/eBHRVXnmljDFDNYvGBC5pALYV1rAA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@ai-sdk/gateway": "2.0.8", - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.17", - "@opentelemetry/api": "1.9.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/ai-v5": { - "name": "ai", - "version": "5.0.76", - "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.76.tgz", - "integrity": "sha512-ZCxi1vrpyCUnDbtYrO/W8GLvyacV9689f00yshTIQ3mFFphbD7eIv40a2AOZBv3GGRA7SSRYIDnr56wcS/gyQg==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/gateway": "2.0.0", - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.12", - "@opentelemetry/api": "1.9.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/ai-v5/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/ai/node_modules/@ai-sdk/gateway": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.8.tgz", - "integrity": "sha512-cA5Sh5pjmsMOlzCxsX9B4bGB9qOn9/HRxKb8ry1OYmrXP3i1t34eZMHA7EVFoB09I41p0LPwkRBACYXm15xokw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.17", - "@vercel/oidc": "3.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/ai/node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/ai/node_modules/@ai-sdk/provider-utils": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz", - "integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/async-mutex": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", - "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.25", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz", - "integrity": "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/builtins": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", - "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.0.0" - } - }, - "node_modules/builtins/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001754", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", - "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true, - "license": "MIT" - }, - "node_modules/compromise": { - "version": "14.14.4", - "resolved": "https://registry.npmjs.org/compromise/-/compromise-14.14.4.tgz", - "integrity": "sha512-QdbJwronwxeqb7a5KFK/+Y5YieZ4PE1f7ai0vU58Pp4jih+soDCBMuKVbhDEPQ+6+vI3vSiG4UAAjTAXLJw1Qw==", - "license": "MIT", - "dependencies": { - "efrt": "2.7.0", - "grad-school": "0.0.5", - "suffix-thumb": "5.0.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/date-fns": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", - "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" - } - }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-match-patch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", - "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", - "license": "Apache-2.0" - }, - "node_modules/difflib": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", - "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==", - "dependencies": { - "heap": ">= 0.2.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/efrt": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/efrt/-/efrt-2.7.0.tgz", - "integrity": "sha512-/RInbCy1d4P6Zdfa+TMVsf/ufZVotat5hCw3QXmWtjU+3pFEOvOQ7ibo3aIxyCJw2leIeAMjmPj+1SLJiCpdrQ==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.250", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz", - "integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/empathic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", - "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/execa": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", - "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.6", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "human-signals": "^8.0.1", - "is-plain-obj": "^4.1.0", - "is-stream": "^4.0.1", - "npm-run-path": "^6.0.0", - "pretty-ms": "^9.2.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": "^18.19.0 || >=20.5.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/express/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/express/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "dev": true, - "license": "MIT" - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/fast-copy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", - "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/figures": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/find-workspaces": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/find-workspaces/-/find-workspaces-0.3.1.tgz", - "integrity": "sha512-UDkGILGJSA1LN5Aa7McxCid4sqW3/e+UYsVwyxki3dDT0F8+ym0rAfnCkEfkL0rO7M+8/mvkim4t/s3IPHmg+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "^3.3.2", - "pkg-types": "^1.0.3", - "yaml": "^2.3.4" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/forwarded-parse": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", - "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", - "license": "MIT" - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gaxios": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", - "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/gaxios/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/gcp-metadata": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", - "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^6.1.1", - "google-logging-utils": "^0.0.2", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-port": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", - "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-stream/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/google-logging-utils": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", - "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/grad-school": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/grad-school/-/grad-school-0.0.5.tgz", - "integrity": "sha512-rXunEHF9M9EkMydTBux7+IryYXEZinRk6g8OBOGDBzo/qWJjhTxy86i5q7lQYpCLHN8Sqv1XX3OIOc7ka2gtvQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/heap": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", - "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", - "license": "MIT" - }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", - "license": "MIT" - }, - "node_modules/hono": { - "version": "4.10.4", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.10.4.tgz", - "integrity": "sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/import-in-the-middle": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", - "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", - "license": "Apache-2.0", - "dependencies": { - "acorn": "^8.14.0", - "acorn-import-attributes": "^1.9.5", - "cjs-module-lexer": "^1.2.2", - "module-details-from-path": "^1.0.3" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-network-error": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", - "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is2": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", - "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "ip-regex": "^4.1.0", - "is-url": "^1.2.4" - }, - "engines": { - "node": ">=v0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/js-base64": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", - "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", - "license": "BSD-3-Clause" - }, - "node_modules/js-tiktoken": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz", - "integrity": "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==", - "license": "MIT", - "dependencies": { - "base64-js": "^1.5.1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-schema-to-zod": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/json-schema-to-zod/-/json-schema-to-zod-2.6.1.tgz", - "integrity": "sha512-uiHmWH21h9FjKJkRBntfVGTLpYlCZ1n98D0izIlByqQLqpmkQpNTBtfbdP04Na6+43lgsvrShFh2uWLkQDKJuQ==", - "license": "ISC", - "bin": { - "json-schema-to-zod": "dist/cjs/cli.js" - } - }, - "node_modules/json-schema-walker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/json-schema-walker/-/json-schema-walker-2.0.0.tgz", - "integrity": "sha512-nXN2cMky0Iw7Af28w061hmxaPDaML5/bQD9nwm1lOoIKEGjHcRGxqWe4MfrkYThYAPjSUhmsp4bJNoLAyVn9Xw==", - "license": "MIT", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^11.1.0", - "clone": "^2.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsondiffpatch": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", - "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", - "license": "MIT", - "dependencies": { - "@types/diff-match-patch": "^1.0.36", - "chalk": "^5.3.0", - "diff-match-patch": "^1.0.5" - }, - "bin": { - "jsondiffpatch": "bin/jsondiffpatch.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyword-extractor": { - "version": "0.0.28", - "resolved": "https://registry.npmjs.org/keyword-extractor/-/keyword-extractor-0.0.28.tgz", - "integrity": "sha512-oi7dSPpYtW/3fE0vZiqQgZ8mW3F1V9K4+rBJ0FcVrdXBEQuhZ0zKj7sX74eqGASuepLHf9aYdeonyKHWhYpHQA==", - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/libsql": { - "version": "0.5.22", - "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.22.tgz", - "integrity": "sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA==", - "cpu": [ - "x64", - "arm64", - "wasm32", - "arm" - ], - "license": "MIT", - "os": [ - "darwin", - "linux", - "win32" - ], - "dependencies": { - "@neon-rs/load": "^0.0.4", - "detect-libc": "2.0.2" - }, - "optionalDependencies": { - "@libsql/darwin-arm64": "0.5.22", - "@libsql/darwin-x64": "0.5.22", - "@libsql/linux-arm-gnueabihf": "0.5.22", - "@libsql/linux-arm-musleabihf": "0.5.22", - "@libsql/linux-arm64-gnu": "0.5.22", - "@libsql/linux-arm64-musl": "0.5.22", - "@libsql/linux-x64-gnu": "0.5.22", - "@libsql/linux-x64-musl": "0.5.22", - "@libsql/win32-x64-msvc": "0.5.22" - } - }, - "node_modules/libsql/node_modules/@neon-rs/load": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", - "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", - "license": "MIT" - }, - "node_modules/libsql/node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/local-pkg/node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/local-pkg/node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/mastra": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/mastra/-/mastra-0.18.0.tgz", - "integrity": "sha512-qfSk/wjxPrw1kpYq4K/XWXl451+mIW6U7d7HlVOeDdlVGTORLXwSDFf7OP7LWOkLCDhkZVYVOObH/4GQ7UcKWg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@clack/prompts": "^0.11.0", - "@expo/devcert": "^1.2.0", - "@mastra/deployer": "^0.24.0", - "@mastra/loggers": "^0.10.19", - "@opentelemetry/auto-instrumentations-node": "^0.62.1", - "@opentelemetry/core": "^2.0.1", - "@opentelemetry/exporter-trace-otlp-grpc": "^0.203.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.203.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/resources": "^2.0.1", - "@opentelemetry/sdk-node": "^0.203.0", - "@opentelemetry/sdk-trace-base": "^2.0.1", - "@opentelemetry/semantic-conventions": "^1.36.0", - "@webcontainer/env": "^1.1.1", - "commander": "^12.1.0", - "dotenv": "^16.6.1", - "execa": "^9.6.0", - "fs-extra": "^11.3.2", - "get-port": "^7.1.0", - "open": "^10.1.2", - "picocolors": "^1.1.1", - "posthog-node": "4.18.0", - "prettier": "^3.6.2", - "shell-quote": "^1.8.3", - "strip-json-comments": "^5.0.3", - "tcp-port-used": "^1.0.2", - "yocto-spinner": "^0.2.3" - }, - "bin": { - "mastra": "dist/index.js" - }, - "peerDependencies": { - "@mastra/core": ">=0.24.0-0 <0.25.0-0", - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/module-details-from-path": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-ensure": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", - "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==", - "license": "MIT" - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/npm-run-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/openapi-types": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT", - "peer": true - }, - "node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.0.tgz", - "integrity": "sha512-xL4PiFRQa/f9L9ZvR4/gUCRNus4N8YX80ku8kv9Jqz+ZokkiZLM0bcvX0gm1F3PDi9SPRsww1BDsTWgE6Y1GLQ==", - "license": "MIT", - "dependencies": { - "is-network-error": "^1.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parse-ms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/pdf-parse": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.4.tgz", - "integrity": "sha512-XRIRcLgk6ZnUbsHsYXExMw+krrPE81hJ6FQPLdBNhhBefqIQKXu/WeTgNBGSwPrfU0v+UCEwn7AoAUOsVKHFvQ==", - "license": "MIT", - "dependencies": { - "node-ensure": "^0.0.0" - }, - "engines": { - "node": ">=6.8.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/mehmet-kozan" - } - }, - "node_modules/pg": { - "version": "8.16.3", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", - "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", - "license": "MIT", - "dependencies": { - "pg-connection-string": "^2.9.1", - "pg-pool": "^3.10.1", - "pg-protocol": "^1.10.3", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.2.7" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", - "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", - "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", - "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", - "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", - "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", - "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "license": "MIT", - "dependencies": { - "split2": "^4.1.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pino": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", - "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", - "license": "MIT", - "dependencies": { - "@pinojs/redact": "^0.4.0", - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-pretty": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.2.tgz", - "integrity": "sha512-3cN0tCakkT4f3zo9RXDIhy6GTvtYD6bK4CRBLN9j3E/ePqN1tugAXD5rGVfoChW6s0hiek+eyYlLNqc/BG7vBQ==", - "license": "MIT", - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^3.0.2", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pump": "^3.0.0", - "secure-json-parse": "^4.0.0", - "sonic-boom": "^4.0.1", - "strip-json-comments": "^5.0.2" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, - "node_modules/pino-std-serializers": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", - "license": "MIT" - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/postgres": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", - "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", - "license": "Unlicense", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/porsager" - } - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/posthog-node": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-4.18.0.tgz", - "integrity": "sha512-XROs1h+DNatgKh/AlIlCtDxWzwrKdYDb2mOs58n4yN8BkGN9ewqeQwG5ApS4/IzwCb7HPttUkOVulkYatd2PIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "axios": "^1.8.2" - }, - "engines": { - "node": ">=15.0.0" - } - }, - "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-ms": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", - "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-ms": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/promise-limit": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", - "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", - "license": "ISC" - }, - "node_modules/protobufjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "license": "MIT" - }, - "node_modules/radash": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/radash/-/radash-12.1.1.tgz", - "integrity": "sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA==", - "license": "MIT", - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/react": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", - "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/redis": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/redis/-/redis-5.9.0.tgz", - "integrity": "sha512-E8dQVLSyH6UE/C9darFuwq4usOPrqfZ1864kI4RFbr5Oj9ioB9qPF0oJMwX7s8mf6sPYrz84x/Dx1PGF3/0EaQ==", - "license": "MIT", - "dependencies": { - "@redis/bloom": "5.9.0", - "@redis/client": "5.9.0", - "@redis/json": "5.9.0", - "@redis/search": "5.9.0", - "@redis/time-series": "5.9.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-in-the-middle": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", - "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "module-details-from-path": "^1.0.3", - "resolve": "^1.22.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.50.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz", - "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.50.2", - "@rollup/rollup-android-arm64": "4.50.2", - "@rollup/rollup-darwin-arm64": "4.50.2", - "@rollup/rollup-darwin-x64": "4.50.2", - "@rollup/rollup-freebsd-arm64": "4.50.2", - "@rollup/rollup-freebsd-x64": "4.50.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.50.2", - "@rollup/rollup-linux-arm-musleabihf": "4.50.2", - "@rollup/rollup-linux-arm64-gnu": "4.50.2", - "@rollup/rollup-linux-arm64-musl": "4.50.2", - "@rollup/rollup-linux-loong64-gnu": "4.50.2", - "@rollup/rollup-linux-ppc64-gnu": "4.50.2", - "@rollup/rollup-linux-riscv64-gnu": "4.50.2", - "@rollup/rollup-linux-riscv64-musl": "4.50.2", - "@rollup/rollup-linux-s390x-gnu": "4.50.2", - "@rollup/rollup-linux-x64-gnu": "4.50.2", - "@rollup/rollup-linux-x64-musl": "4.50.2", - "@rollup/rollup-openharmony-arm64": "4.50.2", - "@rollup/rollup-win32-arm64-msvc": "4.50.2", - "@rollup/rollup-win32-ia32-msvc": "4.50.2", - "@rollup/rollup-win32-x64-msvc": "4.50.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup-plugin-esbuild": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-6.2.1.tgz", - "integrity": "sha512-jTNOMGoMRhs0JuueJrJqbW8tOwxumaWYq+V5i+PD+8ecSCVkuX27tGW7BXqDgoULQ55rO7IdNxPcnsWtshz3AA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "es-module-lexer": "^1.6.0", - "get-tsconfig": "^4.10.0", - "unplugin-utils": "^0.2.4" - }, - "engines": { - "node": ">=14.18.0" - }, - "peerDependencies": { - "esbuild": ">=0.18.0", - "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" - } - }, - "node_modules/rollup-plugin-node-externals": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-externals/-/rollup-plugin-node-externals-8.1.2.tgz", - "integrity": "sha512-EuB6/lolkMLK16gvibUjikERq5fCRVIGwD2xue/CrM8D0pz5GXD2V6N8IrgxegwbcUoKkUFI8VYCEEv8MMvgpA==", - "dev": true, - "funding": [ - { - "type": "patreon", - "url": "https://patreon.com/Septh" - }, - { - "type": "paypal", - "url": "https://paypal.me/septh07" - } - ], - "license": "MIT", - "engines": { - "node": ">= 21 || ^20.6.0 || ^18.19.0" - }, - "peerDependencies": { - "rollup": "^4.0.0" - } - }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/secure-json-parse": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", - "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/sentiment": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/sentiment/-/sentiment-5.0.2.tgz", - "integrity": "sha512-ZeC3y0JsOYTdwujt5uOd7ILJNilbgFzUtg/LEG4wUv43LayFNLZ28ec8+Su+h3saHlJmIwYxBzfDHHZuiMA15g==", - "license": "MIT", - "engines": { - "node": ">=8.0" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sift": { - "version": "17.1.3", - "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", - "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", - "license": "MIT" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/sonic-boom": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string-similarity": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", - "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "ISC" - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-json-comments": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", - "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/suffix-thumb": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/suffix-thumb/-/suffix-thumb-5.0.2.tgz", - "integrity": "sha512-I5PWXAFKx3FYnI9a+dQMWNqTxoRt6vdBdb0O+BJ1sxXCWtSoQCusc13E58f+9p4MYx/qCnEMkD5jac6K2j3dgA==", - "license": "MIT" - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/swr": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", - "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3", - "use-sync-external-store": "^1.4.0" - }, - "peerDependencies": { - "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/tcp-port-used": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", - "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4.3.1", - "is2": "^2.0.6" - } - }, - "node_modules/tcp-port-used/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/tcp-port-used/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - } - }, - "node_modules/throttleit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", - "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-paths": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/typescript-paths/-/typescript-paths-1.5.1.tgz", - "integrity": "sha512-lYErSLCON2MSplVV5V/LBgD4UNjMgY3guATdFCZY2q1Nr6OZEu4q6zX/rYMsG1TaWqqQSszg6C9EU7AGWMDrIw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": "^4.7.2 || ^5" - } - }, - "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/uncrypto": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", - "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unplugin-utils": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.2.5.tgz", - "integrity": "sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pathe": "^2.0.3", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=18.12.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/xstate": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.24.0.tgz", - "integrity": "sha512-h/213ThFfZbOefUWrLc9ZvYggEVBr0jrD2dNxErxNMLQfZRN19v+80TaXFho17hs8Q2E1mULtm/6nv12um0C4A==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/xstate" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/xxhash-wasm": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", - "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", - "license": "MIT" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-spinner": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", - "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18.19" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", - "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-from-json-schema": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/zod-from-json-schema/-/zod-from-json-schema-0.5.1.tgz", - "integrity": "sha512-HZH5mnA6oasGU2mYknkhfZ2LbP2UaRk5cXmgDyc5AZdKvwq/4RY5P/yexPq8vl9rw/3OYvpwMXcDwLag3Tb6DQ==", - "license": "MIT", - "dependencies": { - "zod": "^4.0.17" - } - }, - "node_modules/zod-from-json-schema-v3": { - "name": "zod-from-json-schema", - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/zod-from-json-schema/-/zod-from-json-schema-0.0.5.tgz", - "integrity": "sha512-zYEoo86M1qpA1Pq6329oSyHLS785z/mTwfr9V1Xf/ZLhuuBGaMlDGu/pDVGVUe4H4oa1EFgWZT53DP0U3oT9CQ==", - "license": "MIT", - "dependencies": { - "zod": "^3.24.2" - } - }, - "node_modules/zod-from-json-schema-v3/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/examples/mastra/research-assistant/package.json b/examples/mastra/research-assistant/package.json deleted file mode 100644 index 39ae8f70..00000000 --- a/examples/mastra/research-assistant/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "research-assistant", - "version": "1.0.0", - "main": "index.js", - "scripts": { - "dev": "mastra dev", - "build": "mastra build", - "start": "mastra start", - "ask": "tsx cli/ask.ts" - }, - "keywords": [], - "author": "", - "license": "ISC", - "description": "", - "type": "module", - "engines": { - "node": ">=20.9.0" - }, - "dependencies": { - "@mastra/core": "^0.24.0", - "@mastra/evals": "^0.14.3", - "@mastra/libsql": "^0.16.2", - "@mastra/loggers": "^0.10.19", - "@mastra/memory": "^0.15.11", - "agentfs-sdk": "file:../../../sdk/typescript", - "pdf-parse": "^1.1.1", - "zod": "^4.1.12" - }, - "devDependencies": { - "@types/node": "^24.10.0", - "tsx": "^4.19.1", - "mastra": "^0.18.0", - "typescript": "^5.9.3" - } -} diff --git a/examples/mastra/research-assistant/src/mastra/agents/research-agent.ts b/examples/mastra/research-assistant/src/mastra/agents/research-agent.ts deleted file mode 100644 index 9f71467b..00000000 --- a/examples/mastra/research-assistant/src/mastra/agents/research-agent.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Agent } from '@mastra/core/agent'; -import { Memory } from '@mastra/memory'; -import { LibSQLStore } from '@mastra/libsql'; -import { semanticScholarTool } from '../tools/semantic-tool'; -import { pdfFetchTool, resolvePdfUrlTool } from '../tools/pdf-tool'; - -export const researchAgent = new Agent({ - name: 'Research Assistant', - instructions: ` -You are a database research expert who provides comprehensive, evidence-based analysis. - -When a user asks a research question: - -1. **Search for papers**: Use the 'search-semanticscholar-vldb-sigmod' tool with the user's query to find relevant papers from VLDB and SIGMOD venues. - -2. **Get full paper content**: For the most relevant papers (typically 3-5 papers): - - If a paper has a pdfUrl, use the 'fetch-pdf-text' tool to get the complete paper text - - If a paper only has a regular URL, first try 'resolve-pdf-url' to find the PDF link, then fetch it - - Read the FULL TEXT carefully to extract specific findings, numbers, and comparisons - - If PDF fetching fails, work with the abstract, but note the limitation - -3. **Analyze the content**: Review the full paper texts, focusing on: - - Key findings and performance metrics (extract specific numbers!) - - Comparisons and benchmarks between systems - - Trade-offs and limitations discussed by authors - - Theoretical and practical insights - - Methodologies and experimental setups - -4. **Synthesize a comprehensive answer** that: - - Directly answers the research question with specific evidence from the papers - - Includes concrete performance numbers, comparisons, and trade-offs - - Cites sources using the format (FirstAuthor, Year) - e.g., (Marcus, 2020), (Liu, 2024) - - Uses semicolons for multiple citations: (Author1, Year1; Author2, Year2) - - Highlights areas of agreement or disagreement between papers - - Organizes findings into clear sections with headers like "Key Findings", "Performance Metrics", "Trade-offs" - - Mentions when papers show different results or conflicting evidence - -5. **Include a references section** at the end with full citations in this format: - - AuthorLastName et al. (Year). Paper Title — Venue Year - - URL - -CRITICAL RULES: -- DO NOT just list papers. ANSWER the question with evidence! -- DO NOT give superficial summaries. Include SPECIFIC DETAILS from the papers! -- ALWAYS fetch and read full PDFs for thorough analysis - abstracts alone are insufficient! -- Include ACTUAL NUMBERS and metrics from the papers, not vague descriptions! -- Be thorough, accurate, and evidence-based in your analysis! -`, - model: 'openai/gpt-4o-mini', - tools: { semanticScholarTool, pdfFetchTool, resolvePdfUrlTool }, - memory: new Memory({ - storage: new LibSQLStore({ - url: 'file:../mastra.db', - }), - }), -}); - - diff --git a/examples/mastra/research-assistant/src/mastra/index.ts b/examples/mastra/research-assistant/src/mastra/index.ts deleted file mode 100644 index 070d0022..00000000 --- a/examples/mastra/research-assistant/src/mastra/index.ts +++ /dev/null @@ -1,28 +0,0 @@ - -import { Mastra } from '@mastra/core/mastra'; -import { PinoLogger } from '@mastra/loggers'; -import { LibSQLStore } from '@mastra/libsql'; -import { researchWorkflow } from './workflows/research-workflow'; -import { researchAgent } from './agents/research-agent'; - -export const mastra = new Mastra({ - workflows: { researchWorkflow }, - agents: { researchAgent }, - scorers: {}, - storage: new LibSQLStore({ - // stores observability, scores, ... into memory storage, if it needs to persist, change to file:../mastra.db - url: ":memory:", - }), - logger: new PinoLogger({ - name: 'Mastra', - level: 'info', - }), - telemetry: { - // Telemetry is deprecated and will be removed in the Nov 4th release - enabled: false, - }, - observability: { - // Enables DefaultExporter and CloudExporter for AI tracing - default: { enabled: true }, - }, -}); diff --git a/examples/mastra/research-assistant/src/mastra/scorers/lexical-scorer.ts b/examples/mastra/research-assistant/src/mastra/scorers/lexical-scorer.ts deleted file mode 100644 index b55b1a00..00000000 --- a/examples/mastra/research-assistant/src/mastra/scorers/lexical-scorer.ts +++ /dev/null @@ -1,65 +0,0 @@ -export type RankablePaper = { - paperId: string; - title: string; - authors: string[]; - venue: string; - year: number; - url: string; - abstractText?: string; -}; - -function tokenize(text: string): string[] { - return text - .toLowerCase() - .replace(/[^a-z0-9\s]/g, ' ') - .split(/\s+/) - .filter(Boolean); -} - -function unique(arr: T[]): T[] { - return Array.from(new Set(arr)); -} - -export function computeLexicalScore(query: string, paper: RankablePaper): number { - const queryTokens = unique(tokenize(query)); - const hay = [paper.title, paper.abstractText || ''].join(' '); - const docTokens = tokenize(hay); - - if (docTokens.length === 0 || queryTokens.length === 0) return 0; - - // Simple term overlap weighted by term frequency in the document - const freq: Record = {}; - for (const t of docTokens) { - freq[t] = (freq[t] || 0) + 1; - } - let score = 0; - for (const qt of queryTokens) { - if (freq[qt]) score += 1 + Math.log(1 + freq[qt]); // mild TF weight - } - - // Small recency boost - const currentYear = new Date().getFullYear(); - let recency = 0; - if (paper.year && paper.year >= 1970 && paper.year <= currentYear + 1) { - const age = currentYear - paper.year; - recency = Math.max(0, 1 - Math.min(10, age) / 10); // 0..1 - } - score += 0.1 * recency; - - return score; -} - -export function rankByLexicalScore(query: string, papers: RankablePaper[], k = 5) { - const withScores = papers.map((p) => ({ - paper: p, - score: computeLexicalScore(query, p), - })); - withScores.sort((a, b) => { - if (b.score !== a.score) return b.score - a.score; - if (b.paper.year !== a.paper.year) return b.paper.year - a.paper.year; // recent first - return a.paper.title.localeCompare(b.paper.title); - }); - return withScores.slice(0, k); -} - - diff --git a/examples/mastra/research-assistant/src/mastra/tools/pdf-tool.ts b/examples/mastra/research-assistant/src/mastra/tools/pdf-tool.ts deleted file mode 100644 index c07d1531..00000000 --- a/examples/mastra/research-assistant/src/mastra/tools/pdf-tool.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { createTool } from '@mastra/core/tools'; -import { z } from 'zod'; -import { fetchPdfTextCached, resolvePdfUrl } from '../utils/pdf.js'; -import { getAgentFS } from '../utils/agentfs.js'; - -export const pdfFetchTool = createTool({ - id: 'fetch-pdf-text', - description: - 'Fetches and parses full text content from a research paper PDF URL. Use this when you need the complete paper content for detailed analysis beyond the abstract.', - inputSchema: z.object({ - pdfUrl: z.string().url().describe('Direct URL to the PDF file'), - paperTitle: z.string().describe('Title of the paper (used for caching)'), - year: z.number().describe('Publication year (used for caching)'), - }), - outputSchema: z.object({ - text: z.string().describe('Full text content extracted from the PDF'), - cached: z.boolean().describe('Whether the content was retrieved from cache'), - }), - execute: async ({ context }) => { - const { pdfUrl, paperTitle, year } = context; - - // Generate cache key from title and year - const cacheKey = `${paperTitle}-${year}` - .toLowerCase() - .replace(/[^a-z0-9-_]+/g, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, '') - .slice(0, 150); - - const agentfs = await getAgentFS(); - - // Check if already cached - const txtPath = `/papers/${cacheKey}.txt`; - try { - const cached = await agentfs.fs.readFile(txtPath); - if (cached && cached.length > 300) { - return { text: cached, cached: true }; - } - } catch { - // Not cached, proceed to fetch - } - - // Fetch and parse PDF - const text = await fetchPdfTextCached(cacheKey, pdfUrl); - - if (!text || text.length < 300) { - throw new Error('PDF text extraction failed or returned insufficient content'); - } - - return { text, cached: false }; - }, -}); - -export const resolvePdfUrlTool = createTool({ - id: 'resolve-pdf-url', - description: - 'Attempts to resolve a landing page URL (like a DOI or ACM DL page) to a direct PDF URL. Use this when you have a paper URL but need the PDF link.', - inputSchema: z.object({ - url: z.string().url().describe('Landing page URL for the paper'), - }), - outputSchema: z.object({ - pdfUrl: z.string().url().optional().describe('Resolved direct PDF URL, if found'), - success: z.boolean().describe('Whether PDF URL resolution succeeded'), - }), - execute: async ({ context }) => { - const { url } = context; - const pdfUrl = await resolvePdfUrl(url); - - return { - pdfUrl: pdfUrl || undefined, - success: !!pdfUrl, - }; - }, -}); diff --git a/examples/mastra/research-assistant/src/mastra/tools/semantic-tool.ts b/examples/mastra/research-assistant/src/mastra/tools/semantic-tool.ts deleted file mode 100644 index ce2a5214..00000000 --- a/examples/mastra/research-assistant/src/mastra/tools/semantic-tool.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { createTool } from '@mastra/core/tools'; -import { z } from 'zod'; - -export const paperSchema = z.object({ - paperId: z.string(), - title: z.string(), - authors: z.array(z.string()), - venue: z.string(), - year: z.number(), - url: z.string().url(), - abstractText: z.string().optional(), - pdfUrl: z.string().url().optional(), -}); -export type Paper = z.infer; - -type S2Author = { name?: string }; -type S2Venue = { displayName?: string } | null; -type S2ExternalIds = { DOI?: string | null }; -type S2Paper = { - paperId?: string; - title?: string; - year?: number; - authors?: S2Author[]; - url?: string | null; - abstract?: string | null; - venue?: string | null; - publicationVenue?: S2Venue; - externalIds?: S2ExternalIds; - isOpenAccess?: boolean; - openAccessPdf?: { url?: string | null } | null; -}; - -type S2SearchResponse = { - data?: S2Paper[]; - total?: number; -}; - -function isTargetVenue(venue?: string | null): boolean { - if (!venue) return false; - const v = venue.toLowerCase(); - return ( - v.includes('sigmod') || - v.includes('acm sigmod') || - v.includes('sigmod conference') || - v.includes('proceedings of the vldb endowment') || - v.includes('pvl db') || - v.includes('pvlb') || - v.includes('pvlbd') || - v.includes('vldb') // keep broad to catch PVLDB; risk: VLDB Journal may appear - ); -} - -async function sleep(ms: number) { - return new Promise((r) => setTimeout(r, ms)); -} - -async function fetchWithBackoff(url: string, init: RequestInit, maxRetries = 5, initialDelayMs = 500): Promise { - let attempt = 0; - let delay = initialDelayMs; - while (true) { - const res = await fetch(url, init); - if (res.status !== 429 && res.status < 500) { - return res; - } - if (attempt >= maxRetries) { - return res; - } - // Prefer Retry-After header if present - const retryAfter = res.headers.get('Retry-After'); - let waitMs = delay; - if (retryAfter) { - const secs = Number(retryAfter); - if (!Number.isNaN(secs) && secs > 0) waitMs = secs * 1000; - } - await sleep(waitMs); - attempt += 1; - delay = Math.min(delay * 2, 8000); - } -} - -export async function searchSemanticScholar(query: string): Promise { - const base = 'https://api.semanticscholar.org/graph/v1/paper/search'; - const params = new URLSearchParams({ - query, - limit: '100', - fields: 'title,venue,publicationVenue,year,authors,url,abstract,externalIds,isOpenAccess,openAccessPdf', - }); - const url = `${base}?${params.toString()}`; - const res = await fetchWithBackoff( - url, - { - headers: { - Accept: 'application/json', - 'User-Agent': 'research-assistant', - }, - }, - 5, - 500, - ); - if (!res.ok) { - throw new Error(`Semantic Scholar request failed with status ${res.status}`); - } - const data = (await res.json()) as S2SearchResponse; - const arr = data.data || []; - const mapped: Paper[] = arr - .map((p) => { - const title = (p.title || '').trim(); - const authors = (p.authors || []).map((a) => (a.name || '').trim()).filter(Boolean); - const venue = - (p.publicationVenue?.displayName || p.venue || '').toString().trim(); - const year = Number(p.year || 0); - const doi = p.externalIds?.DOI || null; - const rawUrl = (doi ? `https://doi.org/${doi}` : p.url || '').toString().trim(); - const url = rawUrl; - const paperId = (doi || p.paperId || url || title).toString(); - const abstractText = (p.abstract || undefined)?.toString(); - let pdfUrl = - (p.openAccessPdf?.url || undefined) || - (doi && doi.toLowerCase().startsWith('10.48550/arxiv.') - ? `https://arxiv.org/pdf/${doi.toLowerCase().split('arxiv.')[1]}.pdf` - : undefined); - // Fallback: if the URL is an arXiv abstract, derive the PDF URL - try { - const u = new URL(url); - if (u.host.includes('arxiv.org')) { - const absMatch = u.pathname.match(/^\/abs\/(.+)$/i); - if (absMatch && absMatch[1]) { - pdfUrl = `https://arxiv.org/pdf/${absMatch[1]}.pdf`; - } else { - const pdfMatch = u.pathname.match(/^\/pdf\/(.+)$/i); - if (pdfMatch && pdfMatch[1] && !pdfMatch[1].endsWith('.pdf')) { - pdfUrl = `https://arxiv.org/pdf/${pdfMatch[1]}.pdf`; - } - } - } - } catch { - // ignore URL parse errors - } - return { - paperId, - title, - authors, - venue, - year, - url, - abstractText, - pdfUrl, - } as Paper; - }) - .filter((p) => p.title && p.url); - // Filter to target venues and dedupe by URL - const filtered = mapped.filter((p) => isTargetVenue(p.venue)); - const seen = new Set(); - const deduped = filtered.filter((p) => { - const key = p.url; - if (seen.has(key)) return false; - seen.add(key); - return true; - }); - return deduped; -} - -export const semanticScholarTool = createTool({ - id: 'search-semanticscholar-vldb-sigmod', - description: - 'Search Semantic Scholar for papers relevant to a query, constrained to VLDB (PVLDB) and SIGMOD venues', - inputSchema: z.object({ - query: z.string().describe('User research question or keywords'), - }), - outputSchema: z.object({ - papers: z.array(paperSchema), - }), - execute: async ({ context }) => { - const papers = await searchSemanticScholar(context.query); - return { papers }; - }, -}); - - diff --git a/examples/mastra/research-assistant/src/mastra/utils/agentfs.ts b/examples/mastra/research-assistant/src/mastra/utils/agentfs.ts deleted file mode 100644 index 63cfcf9a..00000000 --- a/examples/mastra/research-assistant/src/mastra/utils/agentfs.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AgentFS } from 'agentfs-sdk'; - -let instance: AgentFS | null = null; - -export async function getAgentFS(): Promise { - if (!instance) { - const id = process.env.AGENTFS_ID || 'research-assistant'; - instance = await AgentFS.open({ id }); - } - return instance; -} - - diff --git a/examples/mastra/research-assistant/src/mastra/utils/pdf.ts b/examples/mastra/research-assistant/src/mastra/utils/pdf.ts deleted file mode 100644 index e46d6145..00000000 --- a/examples/mastra/research-assistant/src/mastra/utils/pdf.ts +++ /dev/null @@ -1,151 +0,0 @@ -import pdfParse from 'pdf-parse'; -import { getAgentFS } from './agentfs.js'; - -async function fetchHtmlWithFallback(url: string): Promise { - const res = await fetch(url, { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', - Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'Accept-Language': 'en-US,en;q=0.9', - Referer: url, - }, - }); - const text = await res.text(); - if (!res.ok || /Access Denied|Robot Check|unusual traffic/i.test(text)) { - try { - const u = new URL(url); - const proxy = `https://r.jina.ai/http://${u.host}${u.pathname}${u.search || ''}`; - const proxRes = await fetch(proxy, { headers: { Accept: 'text/plain' } }); - if (proxRes.ok) { - return await proxRes.text(); - } - } catch { - // ignore - } - } - return text; -} - -export async function fetchPdfText(pdfUrl: string, maxBytes = 20_000_000): Promise { - const res = await fetch(pdfUrl, { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', - Accept: 'application/pdf', - }, - }); - if (!res.ok) { - throw new Error(`PDF fetch failed: ${res.status}`); - } - const buf = Buffer.from(await res.arrayBuffer()); - if (buf.byteLength > maxBytes) { - throw new Error(`PDF too large: ${buf.byteLength} bytes`); - } - const parsed = await pdfParse(buf); - return parsed.text || ''; -} - -export async function fetchPdfTextCached(cacheKey: string, pdfUrl: string, maxBytes = 20_000_000): Promise { - const fs = (await getAgentFS()).fs; - const safeKey = cacheKey.replace(/[^a-z0-9-_]/gi, '_').toLowerCase().slice(0, 200); - const pdfPath = `/papers/${safeKey}.pdf`; - const txtPath = `/papers/${safeKey}.txt`; - // Try text cache - try { - const text = (await fs.readFile(txtPath, 'utf8')) as string; - if (text && text.length > 0) return text; - } catch { - // not cached - } - // Download PDF - const res = await fetch(pdfUrl, { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', - Accept: 'application/pdf', - Referer: pdfUrl, - }, - }); - if (!res.ok) { - throw new Error(`PDF fetch failed: ${res.status}`); - } - const buf = Buffer.from(await res.arrayBuffer()); - if (buf.byteLength > maxBytes) { - throw new Error(`PDF too large: ${buf.byteLength} bytes`); - } - // Cache PDF - try { - await fs.writeFile(pdfPath, buf); - } catch { - // ignore cache write error - } - const parsed = await pdfParse(buf); - const text = parsed.text || ''; - // Cache text - try { - await fs.writeFile(txtPath, text); - } catch { - // ignore - } - return text; -} - -export function clipText(text: string, maxChars = 8000): string { - if (text.length <= maxChars) return text; - return text.slice(0, maxChars); -} - -export async function resolvePdfUrl(landingUrl: string): Promise { - try { - const u = new URL(landingUrl); - // Direct PDF link - if (/\.(pdf)(\?|#|$)/i.test(u.pathname)) { - return landingUrl; - } - // arXiv: handle abs -> pdf and pdf without extension - if (u.host.includes('arxiv.org')) { - const absMatch = u.pathname.match(/^\/abs\/(.+)$/i); - if (absMatch && absMatch[1]) { - return `https://arxiv.org/pdf/${absMatch[1]}.pdf`; - } - const pdfMatch = u.pathname.match(/^\/pdf\/(.+)$/i); - if (pdfMatch && pdfMatch[1] && !pdfMatch[1].endsWith('.pdf')) { - return `https://arxiv.org/pdf/${pdfMatch[1]}.pdf`; - } - } - // ACM DL DOI landing page - if (u.host.includes('dl.acm.org') && /\/doi\//.test(u.pathname)) { - const html = await fetchHtmlWithFallback(landingUrl); - // 1) meta citation_pdf_url - const meta = html.match(/ { - if (!inputData) throw new Error('Input data not found'); - const candidates: Paper[] = await searchSemanticScholar(inputData.question); - return { candidates }; - }, -}); - -const rankStep = createStep({ - id: 'rank-and-select', - description: 'Rank candidates lexically and select top-5', - inputSchema: z.object({ - question: z.string(), - candidates: z.array(paperSchema), - }), - outputSchema: z.object({ - topPapers: z.array(paperWithWhySchema), - }), - execute: async ({ inputData }) => { - if (!inputData) throw new Error('Input data not found'); - const ranked = rankByLexicalScore(inputData.question, inputData.candidates, 5); - const topPapers = ranked.map(({ paper, score }) => { - const why = buildWhy(inputData.question, paper); - return { ...paper, relevance: Number(score.toFixed(3)), why }; - }); - return { topPapers }; - }, -}); - -function buildWhy(question: string, paper: Paper): string { - const qTokens = question - .toLowerCase() - .replace(/[^a-z0-9\s]/g, ' ') - .split(/\s+/) - .filter(Boolean); - const matches = qTokens.filter((t) => paper.title.toLowerCase().includes(t)); - if (matches.length > 0) { - return `Title matches keywords: ${Array.from(new Set(matches)).slice(0, 5).join(', ')}`; - } - return 'Relevant to query within VLDB/SIGMOD; selected via lexical similarity.'; -} - -const researchWorkflow = createWorkflow({ - id: 'research-workflow', - inputSchema: querySchema, - outputSchema: z.object({ - topPapers: z.array(paperWithWhySchema), - }), -}) - .then(searchStep) - .then(rankStep); - -researchWorkflow.commit(); - -export { researchWorkflow }; - - diff --git a/examples/mastra/research-assistant/tsconfig.json b/examples/mastra/research-assistant/tsconfig.json deleted file mode 100644 index f5c0059f..00000000 --- a/examples/mastra/research-assistant/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ES2022", - "moduleResolution": "bundler", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true, - "noEmit": true, - "outDir": "dist" - }, - "include": [ - "src/**/*", - "test/**/*" - ] -} diff --git a/examples/openai-agents/research-assistant/.env.example b/examples/openai-agents/research-assistant/.env.example deleted file mode 100644 index 47c567a2..00000000 --- a/examples/openai-agents/research-assistant/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -# OpenAI API Key (required) -OPENAI_API_KEY=your-api-key-here - -# Optional: Custom AgentFS agent ID -# AGENTFS_ID=research-assistant diff --git a/examples/openai-agents/research-assistant/.gitignore b/examples/openai-agents/research-assistant/.gitignore deleted file mode 100644 index 6407ce90..00000000 --- a/examples/openai-agents/research-assistant/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules/ -dist/ -.env -*.db -*.log - -# AgentFS local databases -.agentfs/ diff --git a/examples/openai-agents/research-assistant/README.md b/examples/openai-agents/research-assistant/README.md deleted file mode 100644 index c55ab403..00000000 --- a/examples/openai-agents/research-assistant/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Research Assistant (VLDB + SIGMOD) - -A minimal research assistant that answers database research questions by finding most relevant papers from the VLDB and SIGMOD conference proceedings. - -## Getting Started - -Install dependencies: - -```console -npm install -``` - -Ask a research question: - -``` -npm run ask -- "How do learned indexes compare to B+ trees?" -``` diff --git a/examples/openai-agents/research-assistant/agentfs.db-wal b/examples/openai-agents/research-assistant/agentfs.db-wal deleted file mode 100644 index 0e6ff645..00000000 Binary files a/examples/openai-agents/research-assistant/agentfs.db-wal and /dev/null differ diff --git a/examples/openai-agents/research-assistant/package-lock.json b/examples/openai-agents/research-assistant/package-lock.json deleted file mode 100644 index 4ebac084..00000000 --- a/examples/openai-agents/research-assistant/package-lock.json +++ /dev/null @@ -1,1961 +0,0 @@ -{ - "name": "research-assistant-openai-agents", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "research-assistant-openai-agents", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@openai/agents": "^0.3.1", - "agentfs-sdk": "file:../../../sdk/typescript", - "dotenv": "^16.4.7", - "pdf-parse": "^1.1.1", - "zod": "^3.24.1" - }, - "bin": { - "research-assistant": "dist/cli.js" - }, - "devDependencies": { - "@types/node": "^24.10.0", - "@types/pdf-parse": "^1.1.4", - "tsx": "^4.19.1", - "typescript": "^5.9.3" - }, - "engines": { - "node": ">=20.9.0" - } - }, - "../../../sdk/typescript": { - "name": "agentfs-sdk", - "version": "0.1.0", - "license": "MIT", - "dependencies": { - "@tursodatabase/database": "^0.3.2" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "@vitest/ui": "^4.0.1", - "typescript": "^5.3.0", - "vitest": "^4.0.1" - } - }, - "../../sdk/typescript": { - "name": "agentfs-sdk", - "version": "0.1.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@tursodatabase/database": "^0.3.2" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "@vitest/ui": "^4.0.1", - "typescript": "^5.3.0", - "vitest": "^4.0.1" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.22.0.tgz", - "integrity": "sha512-VUpl106XVTCpDmTBil2ehgJZjhyLY2QZikzF8NvTXtLRF1CvO5iEE2UNZdVIUer35vFOwMKYeUGbjJtvPWan3g==", - "license": "MIT", - "optional": true, - "dependencies": { - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - } - } - }, - "node_modules/@openai/agents": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@openai/agents/-/agents-0.3.1.tgz", - "integrity": "sha512-m8jw8CRhijRqZEaMhGVIOiXrOpW/n/EZwTV6WTU8eWwpBc2AtvJ7RQADZempNDyg42lCrrMFeofWXus8dlFzLQ==", - "license": "MIT", - "dependencies": { - "@openai/agents-core": "0.3.1", - "@openai/agents-openai": "0.3.1", - "@openai/agents-realtime": "0.3.1", - "debug": "^4.4.0", - "openai": "^6" - }, - "peerDependencies": { - "zod": "^3.25.40 || ^4.0" - } - }, - "node_modules/@openai/agents-core": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@openai/agents-core/-/agents-core-0.3.1.tgz", - "integrity": "sha512-HorYT4IFXYqjdw0DSYM0NnjhKMiEpt1tolMkwVkMH8rl3oH/i/ijEILmwuc/ewrcapNF4+RmOfUblbx2Gn9l0g==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "openai": "^6" - }, - "optionalDependencies": { - "@modelcontextprotocol/sdk": "^1.17.2" - }, - "peerDependencies": { - "zod": "^3.25.40 || ^4.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@openai/agents-openai": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@openai/agents-openai/-/agents-openai-0.3.1.tgz", - "integrity": "sha512-RrMywXdt8MBcTAuT8OP9Xvpl68ddC3IFZ0dqTz6F4U6MPfahejfhMGqn1W4urcZTls+3Xt8KuWldLM+EmKd4Qg==", - "license": "MIT", - "dependencies": { - "@openai/agents-core": "0.3.1", - "debug": "^4.4.0", - "openai": "^6" - }, - "peerDependencies": { - "zod": "^3.25.40 || ^4.0" - } - }, - "node_modules/@openai/agents-realtime": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@openai/agents-realtime/-/agents-realtime-0.3.1.tgz", - "integrity": "sha512-vNxOr6tnRTNRYcC76HAH9UKECaY9+QLMxwjtobk6jZmJnrDVD609+MEvKB32i4Om7B+wgOhtCnZ4pN5blHjRow==", - "license": "MIT", - "dependencies": { - "@openai/agents-core": "0.3.1", - "@types/ws": "^8.18.1", - "debug": "^4.4.0", - "ws": "^8.18.1" - }, - "peerDependencies": { - "zod": "^3.25.40 || ^4.0" - } - }, - "node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/pdf-parse": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@types/pdf-parse/-/pdf-parse-1.1.5.tgz", - "integrity": "sha512-kBfrSXsloMnUJOKi25s3+hRmkycHfLK6A09eRGqF/N8BkQoPUmaCr+q8Cli5FnfohEz/rsv82zAiPz/LXtOGhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "optional": true, - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/agentfs-sdk": { - "resolved": "../../../sdk/typescript", - "link": true - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "optional": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "license": "MIT", - "optional": true, - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "optional": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "license": "MIT", - "optional": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "optional": true, - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "optional": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "optional": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT", - "optional": true - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "optional": true, - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT", - "optional": true - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "license": "MIT", - "optional": true, - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", - "optional": true, - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT", - "optional": true - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "optional": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "optional": true, - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC", - "optional": true - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT", - "optional": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC", - "optional": true - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT", - "optional": true - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "optional": true, - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-ensure": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", - "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==", - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "optional": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "optional": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/openai": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.9.0.tgz", - "integrity": "sha512-n2sJRYmM+xfJ0l3OfH8eNnIyv3nQY7L08gZQu3dw6wSdfPtKAk92L83M2NIP5SS8Cl/bsBBG3yKzEOjkx0O+7A==", - "license": "Apache-2.0", - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/pdf-parse": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.4.tgz", - "integrity": "sha512-XRIRcLgk6ZnUbsHsYXExMw+krrPE81hJ6FQPLdBNhhBefqIQKXu/WeTgNBGSwPrfU0v+UCEwn7AoAUOsVKHFvQ==", - "license": "MIT", - "dependencies": { - "node-ensure": "^0.0.0" - }, - "engines": { - "node": ">=6.8.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/mehmet-kozan" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "optional": true, - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", - "license": "MIT", - "optional": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT", - "optional": true - }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC", - "optional": true - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "optional": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "optional": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "optional": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "optional": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "optional": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "optional": true, - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "optional": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC", - "optional": true - }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "optional": true, - "peerDependencies": { - "zod": "^3.24.1" - } - } - } -} diff --git a/examples/openai-agents/research-assistant/package.json b/examples/openai-agents/research-assistant/package.json deleted file mode 100644 index 927da786..00000000 --- a/examples/openai-agents/research-assistant/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "research-assistant-openai-agents", - "version": "1.0.0", - "description": "A research assistant built with OpenAI Agents SDK that answers database research questions", - "type": "module", - "main": "dist/cli.js", - "bin": { - "research-assistant": "dist/cli.js" - }, - "scripts": { - "build": "tsc", - "ask": "tsx src/cli.ts", - "dev": "tsx src/cli.ts" - }, - "keywords": [ - "openai", - "agent", - "research", - "database", - "ai" - ], - "author": "", - "license": "ISC", - "engines": { - "node": ">=20.9.0" - }, - "dependencies": { - "@openai/agents": "^0.3.1", - "agentfs-sdk": "file:../../../sdk/typescript", - "dotenv": "^16.4.7", - "pdf-parse": "^1.1.1", - "zod": "^3.24.1" - }, - "devDependencies": { - "@types/node": "^24.10.0", - "@types/pdf-parse": "^1.1.4", - "tsx": "^4.19.1", - "typescript": "^5.9.3" - } -} diff --git a/examples/openai-agents/research-assistant/src/agent.ts b/examples/openai-agents/research-assistant/src/agent.ts deleted file mode 100644 index c8c11fa2..00000000 --- a/examples/openai-agents/research-assistant/src/agent.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Agent } from '@openai/agents'; -import { semanticScholarTool } from './tools/semantic-tool.js'; -import { pdfFetchTool, resolvePdfUrlTool } from './tools/pdf-tool.js'; - -export const researchAgent = new Agent({ - name: 'Research Assistant', - instructions: ` -You are a database research expert who provides comprehensive, evidence-based analysis. - -When a user asks a research question: - -1. **Search for papers**: Use the 'search_semanticscholar_vldb_sigmod' tool with the user's query to find relevant papers from VLDB and SIGMOD venues. - -2. **Get full paper content**: For the most relevant papers (typically 3-5 papers): - - If a paper has a pdfUrl, use the 'fetch_pdf_text' tool to get the complete paper text - - If a paper only has a regular URL, first try 'resolve_pdf_url' to find the PDF link, then fetch it - - Read the FULL TEXT carefully to extract specific findings, numbers, and comparisons - - If PDF fetching fails, work with the abstract, but note the limitation - -3. **Analyze the content**: Review the full paper texts, focusing on: - - Key findings and performance metrics (extract specific numbers!) - - Comparisons and benchmarks between systems - - Trade-offs and limitations discussed by authors - - Theoretical and practical insights - - Methodologies and experimental setups - -4. **Synthesize a comprehensive answer** that: - - Directly answers the research question with specific evidence from the papers - - Includes concrete performance numbers, comparisons, and trade-offs - - Cites sources using the format (FirstAuthor, Year) - e.g., (Marcus, 2020), (Liu, 2024) - - Uses semicolons for multiple citations: (Author1, Year1; Author2, Year2) - - Highlights areas of agreement or disagreement between papers - - Organizes findings into clear sections with headers like "Key Findings", "Performance Metrics", "Trade-offs" - - Mentions when papers show different results or conflicting evidence - -5. **Include a references section** at the end with full citations in this format: - - AuthorLastName et al. (Year). Paper Title — Venue Year - - URL - -CRITICAL RULES: -- DO NOT just list papers. ANSWER the question with evidence! -- DO NOT give superficial summaries. Include SPECIFIC DETAILS from the papers! -- ALWAYS fetch and read full PDFs for thorough analysis - abstracts alone are insufficient! -- Include ACTUAL NUMBERS and metrics from the papers, not vague descriptions! -- Be thorough, accurate, and evidence-based in your analysis! -`, - model: 'gpt-4o-mini', - tools: [semanticScholarTool, pdfFetchTool, resolvePdfUrlTool], -}); diff --git a/examples/openai-agents/research-assistant/src/cli.ts b/examples/openai-agents/research-assistant/src/cli.ts deleted file mode 100644 index 69870f12..00000000 --- a/examples/openai-agents/research-assistant/src/cli.ts +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env node -import 'dotenv/config'; -import { run } from '@openai/agents'; -import { researchAgent } from './agent.js'; -import { getAgentFS } from './utils/agentfs.js'; - -async function main() { - const args = process.argv.slice(2); - const question = args.join(' ').trim(); - - if (!question) { - console.error('Usage: npm run ask -- ""'); - process.exit(1); - } - - try { - // Initialize agentfs - await getAgentFS(); - - // Run the agent with the question - const result = await run(researchAgent, question); - - // Output the agent's response - console.log(result.finalOutput); - - } catch (err: any) { - console.error('Error:', err?.message || err); - process.exit(1); - } -} - -main(); diff --git a/examples/openai-agents/research-assistant/src/tools/pdf-tool.ts b/examples/openai-agents/research-assistant/src/tools/pdf-tool.ts deleted file mode 100644 index 64d9071d..00000000 --- a/examples/openai-agents/research-assistant/src/tools/pdf-tool.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { tool } from '@openai/agents'; -import { z } from 'zod'; -import { fetchPdfTextCached, resolvePdfUrl } from '../utils/pdf.js'; -import { getAgentFS } from '../utils/agentfs.js'; - -export const pdfFetchTool = tool({ - name: 'fetch_pdf_text', - description: - 'Fetches and parses full text content from a research paper PDF URL. Use this when you need the complete paper content for detailed analysis beyond the abstract.', - parameters: z.object({ - pdfUrl: z.string().describe('Direct URL to the PDF file'), - paperTitle: z.string().describe('Title of the paper (used for caching)'), - year: z.number().describe('Publication year (used for caching)'), - }), - execute: async ({ pdfUrl, paperTitle, year }) => { - // Generate cache key from title and year - const cacheKey = `${paperTitle}-${year}` - .toLowerCase() - .replace(/[^a-z0-9-_]+/g, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, '') - .slice(0, 150); - - const agentfs = await getAgentFS(); - - // Check if already cached - const txtPath = `/papers/${cacheKey}.txt`; - try { - const cached = await agentfs.fs.readFile(txtPath); - if (cached && cached.length > 300) { - return { text: cached, cached: true }; - } - } catch { - // Not cached, proceed to fetch - } - - // Fetch and parse PDF - const text = await fetchPdfTextCached(cacheKey, pdfUrl); - - if (!text || text.length < 300) { - throw new Error('PDF text extraction failed or returned insufficient content'); - } - - return { text, cached: false }; - }, -}); - -export const resolvePdfUrlTool = tool({ - name: 'resolve_pdf_url', - description: - 'Attempts to resolve a landing page URL (like a DOI or ACM DL page) to a direct PDF URL. Use this when you have a paper URL but need the PDF link.', - parameters: z.object({ - url: z.string().describe('Landing page URL for the paper'), - }), - execute: async ({ url }) => { - const pdfUrl = await resolvePdfUrl(url); - - return { - pdfUrl: pdfUrl || undefined, - success: !!pdfUrl, - }; - }, -}); diff --git a/examples/openai-agents/research-assistant/src/tools/semantic-tool.ts b/examples/openai-agents/research-assistant/src/tools/semantic-tool.ts deleted file mode 100644 index 06cb3028..00000000 --- a/examples/openai-agents/research-assistant/src/tools/semantic-tool.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { tool } from '@openai/agents'; -import { z } from 'zod'; - -export const paperSchema = z.object({ - paperId: z.string(), - title: z.string(), - authors: z.array(z.string()), - venue: z.string(), - year: z.number(), - url: z.string().url(), - abstractText: z.string().optional(), - pdfUrl: z.string().url().optional(), -}); -export type Paper = z.infer; - -type S2Author = { name?: string }; -type S2Venue = { displayName?: string } | null; -type S2ExternalIds = { DOI?: string | null }; -type S2Paper = { - paperId?: string; - title?: string; - year?: number; - authors?: S2Author[]; - url?: string | null; - abstract?: string | null; - venue?: string | null; - publicationVenue?: S2Venue; - externalIds?: S2ExternalIds; - isOpenAccess?: boolean; - openAccessPdf?: { url?: string | null } | null; -}; - -type S2SearchResponse = { - data?: S2Paper[]; - total?: number; -}; - -function isTargetVenue(venue?: string | null): boolean { - if (!venue) return false; - const v = venue.toLowerCase(); - return ( - v.includes('sigmod') || - v.includes('acm sigmod') || - v.includes('sigmod conference') || - v.includes('proceedings of the vldb endowment') || - v.includes('pvl db') || - v.includes('pvlb') || - v.includes('pvlbd') || - v.includes('vldb') // keep broad to catch PVLDB; risk: VLDB Journal may appear - ); -} - -async function sleep(ms: number) { - return new Promise((r) => setTimeout(r, ms)); -} - -async function fetchWithBackoff(url: string, init: RequestInit, maxRetries = 5, initialDelayMs = 500): Promise { - let attempt = 0; - let delay = initialDelayMs; - while (true) { - const res = await fetch(url, init); - if (res.status !== 429 && res.status < 500) { - return res; - } - if (attempt >= maxRetries) { - return res; - } - // Prefer Retry-After header if present - const retryAfter = res.headers.get('Retry-After'); - let waitMs = delay; - if (retryAfter) { - const secs = Number(retryAfter); - if (!Number.isNaN(secs) && secs > 0) waitMs = secs * 1000; - } - await sleep(waitMs); - attempt += 1; - delay = Math.min(delay * 2, 8000); - } -} - -export async function searchSemanticScholar(query: string): Promise { - const base = 'https://api.semanticscholar.org/graph/v1/paper/search'; - const params = new URLSearchParams({ - query, - limit: '100', - fields: 'title,venue,publicationVenue,year,authors,url,abstract,externalIds,isOpenAccess,openAccessPdf', - }); - const url = `${base}?${params.toString()}`; - const res = await fetchWithBackoff( - url, - { - headers: { - Accept: 'application/json', - 'User-Agent': 'research-assistant', - }, - }, - 5, - 500, - ); - if (!res.ok) { - throw new Error(`Semantic Scholar request failed with status ${res.status}`); - } - const data = (await res.json()) as S2SearchResponse; - const arr = data.data || []; - const mapped: Paper[] = arr - .map((p) => { - const title = (p.title || '').trim(); - const authors = (p.authors || []).map((a) => (a.name || '').trim()).filter(Boolean); - const venue = - (p.publicationVenue?.displayName || p.venue || '').toString().trim(); - const year = Number(p.year || 0); - const doi = p.externalIds?.DOI || null; - const rawUrl = (doi ? `https://doi.org/${doi}` : p.url || '').toString().trim(); - const url = rawUrl; - const paperId = (doi || p.paperId || url || title).toString(); - const abstractText = (p.abstract || undefined)?.toString(); - let pdfUrl = - (p.openAccessPdf?.url || undefined) || - (doi && doi.toLowerCase().startsWith('10.48550/arxiv.') - ? `https://arxiv.org/pdf/${doi.toLowerCase().split('arxiv.')[1]}.pdf` - : undefined); - // Fallback: if the URL is an arXiv abstract, derive the PDF URL - try { - const u = new URL(url); - if (u.host.includes('arxiv.org')) { - const absMatch = u.pathname.match(/^\/abs\/(.+)$/i); - if (absMatch && absMatch[1]) { - pdfUrl = `https://arxiv.org/pdf/${absMatch[1]}.pdf`; - } else { - const pdfMatch = u.pathname.match(/^\/pdf\/(.+)$/i); - if (pdfMatch && pdfMatch[1] && !pdfMatch[1].endsWith('.pdf')) { - pdfUrl = `https://arxiv.org/pdf/${pdfMatch[1]}.pdf`; - } - } - } - } catch { - // ignore URL parse errors - } - return { - paperId, - title, - authors, - venue, - year, - url, - abstractText, - pdfUrl, - } as Paper; - }) - .filter((p) => p.title && p.url); - // Filter to target venues and dedupe by URL - const filtered = mapped.filter((p) => isTargetVenue(p.venue)); - const seen = new Set(); - const deduped = filtered.filter((p) => { - const key = p.url; - if (seen.has(key)) return false; - seen.add(key); - return true; - }); - return deduped; -} - -export const semanticScholarTool = tool({ - name: 'search_semanticscholar_vldb_sigmod', - description: - 'Search Semantic Scholar for papers relevant to a query, constrained to VLDB (PVLDB) and SIGMOD venues', - parameters: z.object({ - query: z.string().describe('User research question or keywords'), - }), - execute: async ({ query }) => { - const papers = await searchSemanticScholar(query); - return { papers }; - }, -}); diff --git a/examples/openai-agents/research-assistant/src/utils/agentfs.ts b/examples/openai-agents/research-assistant/src/utils/agentfs.ts deleted file mode 100644 index 5bb79852..00000000 --- a/examples/openai-agents/research-assistant/src/utils/agentfs.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AgentFS } from 'agentfs-sdk'; - -let instance: AgentFS | null = null; - -export async function getAgentFS(): Promise { - if (!instance) { - const id = process.env.AGENTFS_ID || 'research-assistant'; - instance = await AgentFS.open({ id }); - } - return instance; -} diff --git a/examples/openai-agents/research-assistant/src/utils/pdf.ts b/examples/openai-agents/research-assistant/src/utils/pdf.ts deleted file mode 100644 index 91095dda..00000000 --- a/examples/openai-agents/research-assistant/src/utils/pdf.ts +++ /dev/null @@ -1,149 +0,0 @@ -import pdfParse from 'pdf-parse'; -import { getAgentFS } from './agentfs.js'; - -async function fetchHtmlWithFallback(url: string): Promise { - const res = await fetch(url, { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', - Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'Accept-Language': 'en-US,en;q=0.9', - Referer: url, - }, - }); - const text = await res.text(); - if (!res.ok || /Access Denied|Robot Check|unusual traffic/i.test(text)) { - try { - const u = new URL(url); - const proxy = `https://r.jina.ai/http://${u.host}${u.pathname}${u.search || ''}`; - const proxRes = await fetch(proxy, { headers: { Accept: 'text/plain' } }); - if (proxRes.ok) { - return await proxRes.text(); - } - } catch { - // ignore - } - } - return text; -} - -export async function fetchPdfText(pdfUrl: string, maxBytes = 20_000_000): Promise { - const res = await fetch(pdfUrl, { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', - Accept: 'application/pdf', - }, - }); - if (!res.ok) { - throw new Error(`PDF fetch failed: ${res.status}`); - } - const buf = Buffer.from(await res.arrayBuffer()); - if (buf.byteLength > maxBytes) { - throw new Error(`PDF too large: ${buf.byteLength} bytes`); - } - const parsed = await pdfParse(buf); - return parsed.text || ''; -} - -export async function fetchPdfTextCached(cacheKey: string, pdfUrl: string, maxBytes = 20_000_000): Promise { - const fs = (await getAgentFS()).fs; - const safeKey = cacheKey.replace(/[^a-z0-9-_]/gi, '_').toLowerCase().slice(0, 200); - const pdfPath = `/papers/${safeKey}.pdf`; - const txtPath = `/papers/${safeKey}.txt`; - // Try text cache - try { - const text = (await fs.readFile(txtPath, 'utf8')) as string; - if (text && text.length > 0) return text; - } catch { - // not cached - } - // Download PDF - const res = await fetch(pdfUrl, { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', - Accept: 'application/pdf', - Referer: pdfUrl, - }, - }); - if (!res.ok) { - throw new Error(`PDF fetch failed: ${res.status}`); - } - const buf = Buffer.from(await res.arrayBuffer()); - if (buf.byteLength > maxBytes) { - throw new Error(`PDF too large: ${buf.byteLength} bytes`); - } - // Cache PDF - try { - await fs.writeFile(pdfPath, buf); - } catch { - // ignore cache write error - } - const parsed = await pdfParse(buf); - const text = parsed.text || ''; - // Cache text - try { - await fs.writeFile(txtPath, text); - } catch { - // ignore - } - return text; -} - -export function clipText(text: string, maxChars = 8000): string { - if (text.length <= maxChars) return text; - return text.slice(0, maxChars); -} - -export async function resolvePdfUrl(landingUrl: string): Promise { - try { - const u = new URL(landingUrl); - // Direct PDF link - if (/\.(pdf)(\?|#|$)/i.test(u.pathname)) { - return landingUrl; - } - // arXiv: handle abs -> pdf and pdf without extension - if (u.host.includes('arxiv.org')) { - const absMatch = u.pathname.match(/^\/abs\/(.+)$/i); - if (absMatch && absMatch[1]) { - return `https://arxiv.org/pdf/${absMatch[1]}.pdf`; - } - const pdfMatch = u.pathname.match(/^\/pdf\/(.+)$/i); - if (pdfMatch && pdfMatch[1] && !pdfMatch[1].endsWith('.pdf')) { - return `https://arxiv.org/pdf/${pdfMatch[1]}.pdf`; - } - } - // ACM DL DOI landing page - if (u.host.includes('dl.acm.org') && /\/doi\//.test(u.pathname)) { - const html = await fetchHtmlWithFallback(landingUrl); - // 1) meta citation_pdf_url - const meta = html.match(/ = OnceLock::new(); - -/// Global FD tables, one per process (keyed by pid) -static FD_TABLES: OnceLock>> = OnceLock::new(); - -/// Global flag to enable strace-like output -static STRACE_ENABLED: AtomicBool = AtomicBool::new(false); - -/// Initialize the global mount table -/// -/// This must be called before spawning the traced process. -pub fn init_mount_table(table: MountTable) { - MOUNT_TABLE - .set(table) - .expect("Mount table already initialized"); -} - -/// Get a reference to the global mount table -fn get_mount_table() -> &'static MountTable { - MOUNT_TABLE.get().expect("Mount table not initialized") -} - -/// Initialize the global FD tables -/// -/// This must be called before spawning the traced process. -pub fn init_fd_tables() { - FD_TABLES - .set(Mutex::new(HashMap::new())) - .expect("FD tables already initialized"); -} - -/// Initialize strace mode -/// -/// This must be called before spawning the traced process. -pub fn init_strace(enabled: bool) { - STRACE_ENABLED.store(enabled, Ordering::Relaxed); -} - -/// Check if strace is enabled -fn is_strace_enabled() -> bool { - STRACE_ENABLED.load(Ordering::Relaxed) -} - -/// Get or create an FD table for a specific process -fn get_fd_table(pid: i32) -> FdTable { - let tables = FD_TABLES.get().expect("FD tables not initialized"); - let mut tables = tables.lock().unwrap(); - - tables.entry(pid).or_default().clone() -} - -/// Insert an FD table for a specific process (used for fork/clone) -pub(crate) fn insert_fd_table(pid: i32, fd_table: FdTable) { - let tables = FD_TABLES.get().expect("FD tables not initialized"); - let mut tables = tables.lock().unwrap(); - - tables.insert(pid, fd_table); -} - -/// Format a syscall for strace-like output -fn format_syscall(syscall: &Syscall) -> String { - // Using the Debug implementation as a starting point - format!("{:?}", syscall) -} - -/// Format a syscall result for strace-like output -fn format_result(value: i64) -> String { - format!("{}", value) -} - -/// The Sandbox tool -/// -/// This implements the Reverie Tool trait and intercepts syscalls -/// to provide filesystem virtualization. -#[derive(Default)] -pub struct Sandbox {} - -#[reverie::tool] -impl Tool for Sandbox { - type GlobalState = (); - type ThreadState = (); - - async fn handle_syscall_event>( - &self, - guest: &mut T, - syscall: Syscall, - ) -> Result { - let mount_table = get_mount_table(); - let pid = guest.pid().as_raw(); - let fd_table = get_fd_table(pid); - - if is_strace_enabled() { - eprintln!("[{}] {}", pid, format_syscall(&syscall)); - } - - let result = match syscall::dispatch_syscall(guest, syscall, mount_table, &fd_table).await { - Ok(syscall::SyscallResult::Value(value)) => { - if is_strace_enabled() { - eprintln!("[{}] = {}", pid, format_result(value)); - } - Ok(value) - } - Ok(syscall::SyscallResult::Syscall(syscall)) => guest.tail_inject(syscall).await, - Err(e) => { - if is_strace_enabled() { - if let Error::Errno(errno) = &e { - eprintln!("[{}] = -1 {}", pid, errno); - } else { - eprintln!("[{}] = error: {:?}", pid, e); - } - } - Err(e) - } - }; - - result - } -} diff --git a/sandbox/src/syscall/file.rs b/sandbox/src/syscall/file.rs deleted file mode 100644 index dc0f69d2..00000000 --- a/sandbox/src/syscall/file.rs +++ /dev/null @@ -1,1876 +0,0 @@ -use crate::{ - sandbox::Sandbox, - syscall::translate_path, - vfs::{ - fdtable::{FdEntry, FdTable}, - mount::MountTable, - }, -}; -use reverie::{ - syscalls::{MemoryAccess, ReadAddr, Syscall}, - Error, Guest, Stack, -}; -use std::mem::MaybeUninit; - -/// The `openat` system call. -/// -/// This intercepts `openat` system calls and translates paths according to the mount table, -/// virtualizes the dirfd parameter, and virtualizes the returned file descriptor. -/// -/// Returns `Some(result)` if the syscall was handled and the result should be returned directly, -/// or `None` if the original syscall should be used. -pub async fn handle_openat>( - guest: &mut T, - args: &reverie::syscalls::Openat, - mount_table: &MountTable, - fd_table: &FdTable, -) -> Result, Error> { - if let Some(path_addr) = args.path() { - // Read the original path from guest memory - let mut path: std::path::PathBuf = path_addr.read(&guest.memory())?; - - // Handle dirfd resolution for relative paths - let dirfd = args.dirfd(); - let kernel_dirfd = if dirfd == libc::AT_FDCWD { - dirfd - } else if path.is_relative() { - // For relative paths, resolve against dirfd - if let Some(dir_entry) = fd_table.get(dirfd) { - // Check if this is a passthrough directory with a kernel FD first - if let Some(kfd) = dir_entry.kernel_fd() { - // Passthrough directory - use the kernel FD and keep path as-is - kfd - } else if let Some(dir_path) = dir_entry.path() { - // Virtual directory - resolve relative path against the directory's path - path = dir_path.join(&path); - // For virtual directories, we'll use AT_FDCWD since we have the full path now - libc::AT_FDCWD - } else { - // Virtual file without a path - this shouldn't happen for directories - return Ok(Some(-libc::EBADF as i64)); - } - } else { - // dirfd not in table - will likely fail - dirfd - } - } else { - // Absolute path - dirfd is ignored, use AT_FDCWD - libc::AT_FDCWD - }; - - // Check if this path matches a mount point - if let Some((vfs, _translated_path)) = mount_table.resolve(&path) { - // Check if this is a virtual VFS (like SQLite) - if vfs.is_virtual() { - // For virtual VFS, open the file directly without going to the kernel - let mode = args.mode().map(|m| m.bits()).unwrap_or(0o644); - match vfs.open(&path, args.flags().bits(), mode).await { - Ok(file_ops) => { - // Store the path with the FD entry for directories - let entry = FdEntry::Virtual { - file_ops, - flags: args.flags().bits(), - path: Some(path.clone()), - }; - let virtual_fd = fd_table.allocate(entry); - return Ok(Some(virtual_fd as i64)); - } - Err(e) => { - // Map VFS errors to errno - let errno = match e { - crate::vfs::VfsError::NotFound => -libc::ENOENT as i64, - crate::vfs::VfsError::PermissionDenied => -libc::EACCES as i64, - _ => -libc::EIO as i64, - }; - return Ok(Some(errno)); - } - } - } else { - // For passthrough VFS, translate the path and call the kernel - let new_path_addr = translate_path(guest, path_addr, mount_table).await?; - - let new_syscall = reverie::syscalls::Openat::new() - .with_dirfd(kernel_dirfd) - .with_path(new_path_addr.or(Some(path_addr))) - .with_flags(args.flags()) - .with_mode(args.mode()); - - let kernel_fd = guest.inject(Syscall::Openat(new_syscall)).await?; - - if kernel_fd >= 0 { - // Mounted path - create passthrough FD entry - let entry = FdEntry::Passthrough { - kernel_fd: kernel_fd as i32, - flags: args.flags().bits(), - path: Some(path.clone()), - }; - let virtual_fd = fd_table.allocate(entry); - return Ok(Some(virtual_fd as i64)); - } else { - return Ok(Some(kernel_fd)); - } - } - } else { - // No mount point matches - pass through to kernel with original path - let new_syscall = reverie::syscalls::Openat::new() - .with_dirfd(kernel_dirfd) - .with_path(Some(path_addr)) - .with_flags(args.flags()) - .with_mode(args.mode()); - - let kernel_fd = guest.inject(Syscall::Openat(new_syscall)).await?; - - if kernel_fd >= 0 { - // Unmounted path - create passthrough FD entry - let entry = FdEntry::Passthrough { - kernel_fd: kernel_fd as i32, - flags: args.flags().bits(), - path: Some(path.clone()), - }; - let virtual_fd = fd_table.allocate(entry); - return Ok(Some(virtual_fd as i64)); - } else { - return Ok(Some(kernel_fd)); - } - } - } - Ok(None) -} - -/// The `read` system call. -/// -/// This intercepts `read` system calls and translates virtual FDs to kernel FDs, -/// or calls FileOps directly for virtual files. -pub async fn handle_read>( - guest: &mut T, - syscall: Syscall, - args: &reverie::syscalls::Read, - fd_table: &FdTable, -) -> Result { - let virtual_fd = args.fd(); - - // Get the FD entry - if let Some(entry) = fd_table.get(virtual_fd) { - match entry { - FdEntry::Passthrough { kernel_fd, .. } => { - // Passthrough file - rewrite FD and return modified syscall for tail_inject - let new_syscall = args.with_fd(kernel_fd); - - return Ok(crate::syscall::SyscallResult::Syscall(Syscall::Read( - new_syscall, - ))); - } - FdEntry::Virtual { file_ops, .. } => { - // Virtual file - use FileOps directly - let buf_addr = match args.buf() { - Some(addr) => addr, - None => return Ok(crate::syscall::SyscallResult::Value(-libc::EFAULT as i64)), - }; - - let buf_len = args.len(); - let mut buf = vec![0u8; buf_len]; - - match file_ops.read(&mut buf).await { - Ok(n) => { - // Write the data back to guest memory - if n > 0 { - guest.memory().write_exact(buf_addr, &buf[..n])?; - } - return Ok(crate::syscall::SyscallResult::Value(n as i64)); - } - Err(e) => { - // Map VFS errors to errno - let errno = match e { - crate::vfs::VfsError::NotFound => -libc::ENOENT as i64, - crate::vfs::VfsError::PermissionDenied => -libc::EACCES as i64, - _ => -libc::EIO as i64, - }; - return Ok(crate::syscall::SyscallResult::Value(errno)); - } - } - } - } - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(crate::syscall::SyscallResult::Syscall(syscall)) -} - -/// The `write` system call. -/// -/// This intercepts `write` system calls and translates virtual FDs to kernel FDs, -/// or calls FileOps directly for virtual files. -pub async fn handle_write>( - guest: &mut T, - syscall: Syscall, - args: &reverie::syscalls::Write, - fd_table: &FdTable, -) -> Result { - let virtual_fd = args.fd(); - - // Get the FD entry - if let Some(entry) = fd_table.get(virtual_fd) { - match entry { - FdEntry::Passthrough { kernel_fd, .. } => { - // Passthrough file - rewrite FD and return modified syscall for tail_inject - let new_syscall = args.with_fd(kernel_fd); - - return Ok(crate::syscall::SyscallResult::Syscall(Syscall::Write( - new_syscall, - ))); - } - FdEntry::Virtual { file_ops, .. } => { - // Virtual file - use FileOps directly - let buf_addr = match args.buf() { - Some(addr) => addr, - None => return Ok(crate::syscall::SyscallResult::Value(-libc::EFAULT as i64)), - }; - - let buf_len = args.len(); - let mut buf = vec![0u8; buf_len]; - - // Read data from guest memory - guest.memory().read_exact(buf_addr, &mut buf)?; - - match file_ops.write(&buf).await { - Ok(n) => { - return Ok(crate::syscall::SyscallResult::Value(n as i64)); - } - Err(e) => { - // Map VFS errors to errno - let errno = match e { - crate::vfs::VfsError::NotFound => -libc::ENOENT as i64, - crate::vfs::VfsError::PermissionDenied => -libc::EACCES as i64, - _ => -libc::EIO as i64, - }; - return Ok(crate::syscall::SyscallResult::Value(errno)); - } - } - } - } - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(crate::syscall::SyscallResult::Syscall(syscall)) -} - -/// The `close` system call. -/// -/// This intercepts `close` system calls, translates virtual FDs to kernel FDs, -/// and cleans up the FD mapping. -pub async fn handle_close>( - _guest: &mut T, - syscall: Syscall, - args: &reverie::syscalls::Close, - fd_table: &FdTable, -) -> Result { - let virtual_fd = args.fd(); - - // Translate and deallocate the virtual FD - if let Some(entry) = fd_table.deallocate(virtual_fd) { - match entry { - FdEntry::Passthrough { kernel_fd, .. } => { - // Passthrough file - rewrite FD and return modified syscall for tail_inject - let new_syscall = args.with_fd(kernel_fd); - - return Ok(crate::syscall::SyscallResult::Syscall(Syscall::Close( - new_syscall, - ))); - } - FdEntry::Virtual { file_ops, .. } => { - // Virtualized file - just call close on the FileOps - file_ops.close().await.ok(); - return Ok(crate::syscall::SyscallResult::Value(0)); // Success - } - } - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(crate::syscall::SyscallResult::Syscall(syscall)) -} - -/// The `dup` system call. -/// -/// This intercepts `dup` system calls and duplicates both the virtual and kernel FDs. -pub async fn handle_dup>( - guest: &mut T, - args: &reverie::syscalls::Dup, - fd_table: &FdTable, -) -> Result, Error> { - let old_vfd = args.oldfd(); - - // Get the old entry to preserve flags - if let Some(old_entry) = fd_table.get(old_vfd) { - match old_entry { - FdEntry::Passthrough { - kernel_fd, - flags, - path, - } => { - // Duplicate the kernel FD at the kernel level first - let new_syscall = reverie::syscalls::Dup::new().with_oldfd(kernel_fd); - let result = guest.inject(Syscall::Dup(new_syscall)).await?; - - if result < 0 { - return Ok(Some(result)); - } - - // Create a new passthrough FD entry with the new kernel FD - let new_kernel_fd = result as i32; - let entry = FdEntry::Passthrough { - kernel_fd: new_kernel_fd, - flags, - path, - }; - - // Allocate a new virtual FD - let new_vfd = fd_table.allocate(entry); - return Ok(Some(new_vfd as i64)); - } - FdEntry::Virtual { .. } => { - // Virtualized file - just duplicate the virtual FD - if let Some(new_vfd) = fd_table.duplicate(old_vfd) { - return Ok(Some(new_vfd as i64)); - } - } - } - } - - // FD not in table, let the original syscall through - Ok(None) -} - -/// The `dup2` system call. -/// -/// This intercepts `dup2` system calls and handles virtual FD duplication. -#[cfg(not(target_arch = "aarch64"))] -pub async fn handle_dup2>( - guest: &mut T, - args: &reverie::syscalls::Dup2, - fd_table: &FdTable, -) -> Result, Error> { - let old_vfd = args.oldfd(); - let new_vfd = args.newfd(); - - // Get the entry for the old virtual FD - if let Some(old_entry) = fd_table.get(old_vfd) { - // Get the entry at new_vfd if it exists (we need to close its kernel FD) - let old_new_entry = fd_table.get(new_vfd); - - match old_entry { - FdEntry::Passthrough { - kernel_fd: old_kernel_fd, - flags: _, - path, - } => { - // Allocate a new kernel FD - we need to duplicate to a fresh FD first, - // then close the old one if needed, to avoid race conditions - let new_kernel_fd = guest - .inject(Syscall::Dup( - reverie::syscalls::Dup::new().with_oldfd(old_kernel_fd), - )) - .await?; - - if new_kernel_fd < 0 { - // Dup failed, return the error - return Ok(Some(new_kernel_fd)); - } - - // Close the old kernel FD at new_vfd if it existed - if let Some(old_entry) = old_new_entry { - match old_entry { - FdEntry::Passthrough { kernel_fd, .. } => { - let _ = guest - .inject(Syscall::Close( - reverie::syscalls::Close::new().with_fd(kernel_fd), - )) - .await?; - } - FdEntry::Virtual { file_ops, .. } => { - // Close the FileOps if it's a virtualized file - file_ops.close().await.ok(); - } - } - } - - // Create new passthrough FD entry for the duplicated kernel FD - let entry = FdEntry::Passthrough { - kernel_fd: new_kernel_fd as i32, - flags: 0, - path, - }; - let _ = fd_table.allocate_at(new_vfd, entry); - } - FdEntry::Virtual { .. } => { - // Virtualized file - close old entry at new_vfd if exists, then duplicate - if let Some(old_entry) = old_new_entry { - match old_entry { - FdEntry::Virtual { file_ops, .. } => { - file_ops.close().await.ok(); - } - FdEntry::Passthrough { kernel_fd, .. } => { - let _ = guest - .inject(Syscall::Close( - reverie::syscalls::Close::new().with_fd(kernel_fd), - )) - .await?; - } - } - } - let _ = fd_table.allocate_at(new_vfd, old_entry); - } - } - - Ok(Some(new_vfd as i64)) - } else { - // FD not in table, let the original syscall through - Ok(None) - } -} - -/// The `dup3` system call. -/// -/// This intercepts `dup3` system calls and handles virtual FD duplication with flags. -pub async fn handle_dup3>( - guest: &mut T, - args: &reverie::syscalls::Dup3, - fd_table: &FdTable, -) -> Result, Error> { - let old_vfd = args.oldfd(); - let new_vfd = args.newfd(); - let flags = args.flags(); - - // Get the entry for the old virtual FD - if let Some(old_entry) = fd_table.get(old_vfd) { - // Get the entry at new_vfd if it exists (we need to close its kernel FD) - let old_new_entry = fd_table.get(new_vfd); - - match old_entry { - FdEntry::Passthrough { - kernel_fd: old_kernel_fd, - path, - .. - } => { - // Allocate a new kernel FD - we need to duplicate to a fresh FD first, - // then close the old one if needed, to avoid race conditions - let new_kernel_fd = guest - .inject(Syscall::Dup( - reverie::syscalls::Dup::new().with_oldfd(old_kernel_fd), - )) - .await?; - - if new_kernel_fd < 0 { - // Dup failed, return the error - return Ok(Some(new_kernel_fd)); - } - - // Close the old kernel FD at new_vfd if it existed - if let Some(old_entry) = old_new_entry { - match old_entry { - FdEntry::Passthrough { kernel_fd, .. } => { - let _ = guest - .inject(Syscall::Close( - reverie::syscalls::Close::new().with_fd(kernel_fd), - )) - .await?; - } - FdEntry::Virtual { file_ops, .. } => { - file_ops.close().await.ok(); - } - } - } - - // Note: dup3 flags (O_CLOEXEC) are stored in the FD table and will be - // applied later when needed (e.g., on exec). The kernel FD itself doesn't - // need the flag since we're virtualizing the behavior. - - // Create new passthrough FD entry for the duplicated kernel FD - let entry = FdEntry::Passthrough { - kernel_fd: new_kernel_fd as i32, - flags: flags.bits(), - path, - }; - let _ = fd_table.allocate_at(new_vfd, entry); - } - FdEntry::Virtual { .. } => { - // Virtualized file - close old entry at new_vfd if exists, then duplicate - if let Some(old_entry) = old_new_entry { - match old_entry { - FdEntry::Virtual { file_ops, .. } => { - file_ops.close().await.ok(); - } - FdEntry::Passthrough { kernel_fd, .. } => { - let _ = guest - .inject(Syscall::Close( - reverie::syscalls::Close::new().with_fd(kernel_fd), - )) - .await?; - } - } - } - let entry = match old_entry { - FdEntry::Virtual { file_ops, path, .. } => FdEntry::Virtual { - file_ops, - flags: flags.bits(), - path, - }, - _ => unreachable!(), - }; - let _ = fd_table.allocate_at(new_vfd, entry); - } - } - - Ok(Some(new_vfd as i64)) - } else { - // FD not in table, let the original syscall through - Ok(None) - } -} - -/// The `ioctl` system call. -/// -/// This intercepts `ioctl` system calls and translates virtual FDs to kernel FDs. -/// This is crucial for terminal control operations like job control in shells. -pub async fn handle_ioctl>( - guest: &mut T, - args: &reverie::syscalls::Ioctl, - fd_table: &FdTable, -) -> Result, Error> { - let virtual_fd = args.fd(); - - // Translate virtual FD to kernel FD - if let Some(kernel_fd) = fd_table.translate(virtual_fd) { - // If FDs are identical (common for stdin/stdout/stderr), pass through - if virtual_fd == kernel_fd { - return Ok(None); - } - - // Create a new syscall with the translated kernel FD - let new_syscall = reverie::syscalls::Ioctl::new() - .with_fd(kernel_fd) - .with_request(args.request()); - - let result = guest.inject(Syscall::Ioctl(new_syscall)).await?; - return Ok(Some(result)); - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(None) -} - -/// The `fcntl` system call. -/// -/// This intercepts `fcntl` system calls and handles virtual FD operations. -/// Special handling is needed for F_DUPFD and F_DUPFD_CLOEXEC commands which -/// duplicate file descriptors. -pub async fn handle_fcntl>( - guest: &mut T, - args: &reverie::syscalls::Fcntl, - fd_table: &FdTable, -) -> Result, Error> { - use reverie::syscalls::FcntlCmd; - - let virtual_fd = args.fd(); - - // Translate virtual FD to kernel FD - if let Some(kernel_fd) = fd_table.translate(virtual_fd) { - match args.cmd() { - FcntlCmd::F_DUPFD(arg) | FcntlCmd::F_DUPFD_CLOEXEC(arg) => { - // For dup commands, we need to: - // 1. Execute the syscall with the kernel FD to get a new kernel FD - // 2. Allocate a new virtual FD for the result - - let is_cloexec = matches!(args.cmd(), FcntlCmd::F_DUPFD_CLOEXEC(_)); - // O_CLOEXEC = 0o2000000 on Linux - let flags = if is_cloexec { 0o2000000 } else { 0 }; - - // Translate the arg if it's a virtual FD (it specifies minimum FD number) - // For now, we'll use 0 as the minimum for the kernel FD since we're virtualizing - let kernel_arg = 0; - - let new_cmd = if is_cloexec { - FcntlCmd::F_DUPFD_CLOEXEC(kernel_arg) - } else { - FcntlCmd::F_DUPFD(kernel_arg) - }; - - let new_syscall = reverie::syscalls::Fcntl::new() - .with_fd(kernel_fd) - .with_cmd(new_cmd); - - let new_kernel_fd = guest.inject(Syscall::Fcntl(new_syscall)).await?; - - // If the syscall succeeded, allocate a new virtual FD - if new_kernel_fd >= 0 { - // Get the old entry to preserve the path - let old_entry = fd_table.get(virtual_fd); - let fd_path = old_entry.as_ref().and_then(|e| e.path()); - - // Create passthrough FD entry for the new kernel FD - let entry = FdEntry::Passthrough { - kernel_fd: new_kernel_fd as i32, - flags, - path: fd_path.cloned(), - }; - // Allocate virtual FD at or above the requested minimum - let new_vfd = fd_table.allocate_min(arg, entry); - return Ok(Some(new_vfd as i64)); - } else { - // Return the error code as-is - return Ok(Some(new_kernel_fd)); - } - } - _ => { - // For other fcntl commands, just translate the FD and pass through - let new_syscall = reverie::syscalls::Fcntl::new() - .with_fd(kernel_fd) - .with_cmd(args.cmd()); - - let result = guest.inject(Syscall::Fcntl(new_syscall)).await?; - return Ok(Some(result)); - } - } - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(None) -} - -/// Helper functions for working with fd_set -mod fdset { - use super::*; - - /// Check if a file descriptor is set in an fd_set - #[inline] - pub fn is_set(fd: i32, set: &libc::fd_set) -> bool { - unsafe { libc::FD_ISSET(fd, set as *const _ as *mut _) } - } - - /// Set a file descriptor in an fd_set - #[inline] - pub fn set(fd: i32, set: &mut libc::fd_set) { - unsafe { libc::FD_SET(fd, set) } - } - - /// Clear an fd_set - #[inline] - pub fn zero(set: &mut libc::fd_set) { - unsafe { libc::FD_ZERO(set) } - } - - /// Translate an fd_set from virtual FDs to kernel FDs - pub fn translate_to_kernel( - virt_set: &libc::fd_set, - virt_nfds: i32, - fd_table: &FdTable, - ) -> (libc::fd_set, i32) { - let mut kernel_set: libc::fd_set = unsafe { MaybeUninit::zeroed().assume_init() }; - zero(&mut kernel_set); - let mut max_kernel_fd = 0; - - for vfd in 0..virt_nfds { - if is_set(vfd, virt_set) { - if let Some(kfd) = fd_table.translate(vfd) { - set(kfd, &mut kernel_set); - if kfd > max_kernel_fd { - max_kernel_fd = kfd; - } - } - } - } - - (kernel_set, max_kernel_fd + 1) - } - - /// Translate an fd_set from kernel FDs back to virtual FDs - pub fn translate_to_virtual( - kernel_set: &libc::fd_set, - kernel_nfds: i32, - virt_set: &mut libc::fd_set, - virt_nfds: i32, - fd_table: &FdTable, - ) { - zero(virt_set); - - // Iterate through virtual FDs and check if their kernel FD is set - for vfd in 0..virt_nfds { - if let Some(kfd) = fd_table.translate(vfd) { - if kfd < kernel_nfds && is_set(kfd, kernel_set) { - set(vfd, virt_set); - } - } - } - } -} - -/// The `pselect6` system call. -/// -/// This intercepts `pselect6` system calls and translates virtual FDs in the fd_sets -/// to kernel FDs before calling the real syscall, then translates the results back. -pub async fn handle_pselect6>( - guest: &mut T, - args: &reverie::syscalls::Pselect6, - fd_table: &FdTable, -) -> Result, Error> { - let virt_nfds = args.nfds(); - - // Read the virtual fd_sets from guest memory - let virt_readfds = if let Some(addr) = args.readfds() { - Some(guest.memory().read_value(addr)?) - } else { - None - }; - - let virt_writefds = if let Some(addr) = args.writefds() { - Some(guest.memory().read_value(addr)?) - } else { - None - }; - - let virt_exceptfds = if let Some(addr) = args.exceptfds() { - Some(guest.memory().read_value(addr)?) - } else { - None - }; - - // Translate fd_sets from virtual to kernel FDs - let (kernel_readfds, max_read) = if let Some(ref vset) = virt_readfds { - let (kset, max) = fdset::translate_to_kernel(vset, virt_nfds, fd_table); - (Some(kset), max) - } else { - (None, 0) - }; - - let (kernel_writefds, max_write) = if let Some(ref vset) = virt_writefds { - let (kset, max) = fdset::translate_to_kernel(vset, virt_nfds, fd_table); - (Some(kset), max) - } else { - (None, 0) - }; - - let (kernel_exceptfds, max_except) = if let Some(ref vset) = virt_exceptfds { - let (kset, max) = fdset::translate_to_kernel(vset, virt_nfds, fd_table); - (Some(kset), max) - } else { - (None, 0) - }; - - // Calculate the maximum kernel FD + 1 - let kernel_nfds = max_read.max(max_write).max(max_except); - - // If all fd_sets are None or nfds is 0, just pass through - if kernel_nfds == 0 { - return Ok(None); - } - - // Allocate space for kernel fd_sets in guest memory - let mut stack = guest.stack().await; - - let kernel_readfds_addr = if kernel_readfds.is_some() { - let addr: reverie::syscalls::AddrMut = stack.reserve(); - Some(addr) - } else { - None - }; - - let kernel_writefds_addr = if kernel_writefds.is_some() { - let addr: reverie::syscalls::AddrMut = stack.reserve(); - Some(addr) - } else { - None - }; - - let kernel_exceptfds_addr = if kernel_exceptfds.is_some() { - let addr: reverie::syscalls::AddrMut = stack.reserve(); - Some(addr) - } else { - None - }; - - stack.commit()?; - - // Write kernel fd_sets to guest memory - if let (Some(kset), Some(addr)) = (kernel_readfds.as_ref(), kernel_readfds_addr) { - guest.memory().write_value(addr, kset)?; - } - if let (Some(kset), Some(addr)) = (kernel_writefds.as_ref(), kernel_writefds_addr) { - guest.memory().write_value(addr, kset)?; - } - if let (Some(kset), Some(addr)) = (kernel_exceptfds.as_ref(), kernel_exceptfds_addr) { - guest.memory().write_value(addr, kset)?; - } - - // Create new syscall with translated FDs - let new_syscall = reverie::syscalls::Pselect6::new() - .with_nfds(kernel_nfds) - .with_readfds(kernel_readfds_addr) - .with_writefds(kernel_writefds_addr) - .with_exceptfds(kernel_exceptfds_addr) - .with_timeout(args.timeout()) - .with_sigmask(args.sigmask()); - - // Execute the syscall - let result = guest.inject(Syscall::Pselect6(new_syscall)).await?; - - // If the syscall failed or timed out, return early - if result <= 0 { - return Ok(Some(result)); - } - - // Read back the kernel fd_sets and translate to virtual FDs - if let (Some(addr), Some(_)) = (kernel_readfds_addr, virt_readfds.as_ref()) { - let kernel_set: libc::fd_set = guest.memory().read_value(addr)?; - let mut virt_set: libc::fd_set = unsafe { MaybeUninit::zeroed().assume_init() }; - fdset::translate_to_virtual(&kernel_set, kernel_nfds, &mut virt_set, virt_nfds, fd_table); - - // Write back to original guest address - if let Some(orig_addr) = args.readfds() { - guest.memory().write_value(orig_addr, &virt_set)?; - } - } - - if let (Some(addr), Some(_)) = (kernel_writefds_addr, virt_writefds.as_ref()) { - let kernel_set: libc::fd_set = guest.memory().read_value(addr)?; - let mut virt_set: libc::fd_set = unsafe { MaybeUninit::zeroed().assume_init() }; - fdset::translate_to_virtual(&kernel_set, kernel_nfds, &mut virt_set, virt_nfds, fd_table); - - if let Some(orig_addr) = args.writefds() { - guest.memory().write_value(orig_addr, &virt_set)?; - } - } - - if let (Some(addr), Some(_)) = (kernel_exceptfds_addr, virt_exceptfds.as_ref()) { - let kernel_set: libc::fd_set = guest.memory().read_value(addr)?; - let mut virt_set: libc::fd_set = unsafe { MaybeUninit::zeroed().assume_init() }; - fdset::translate_to_virtual(&kernel_set, kernel_nfds, &mut virt_set, virt_nfds, fd_table); - - if let Some(orig_addr) = args.exceptfds() { - guest.memory().write_value(orig_addr, &virt_set)?; - } - } - - Ok(Some(result)) -} - -/// The `poll` system call. -/// -/// This intercepts `poll` system calls and translates virtual FDs in the pollfd array -/// to kernel FDs before calling the real syscall, then translates the results back. -#[cfg(not(target_arch = "aarch64"))] -pub async fn handle_poll>( - guest: &mut T, - args: &reverie::syscalls::Poll, - fd_table: &FdTable, -) -> Result, Error> { - use reverie::syscalls::{MemoryAccess, PollFd}; - - let nfds = args.nfds(); - if nfds == 0 { - return Ok(None); - } - - let fds_addr = match args.fds() { - Some(addr) => addr, - None => return Ok(None), - }; - - // Read the pollfd array from guest memory - let mut pollfds: Vec = Vec::with_capacity(nfds as usize); - for i in 0..nfds { - let offset = i as isize * std::mem::size_of::() as isize; - let pollfd: PollFd = unsafe { guest.memory().read_value(fds_addr.offset(offset))? }; - pollfds.push(pollfd); - } - - // Allocate space on stack for kernel pollfd array - let mut stack = guest.stack().await; - let kernel_fds_addr: reverie::syscalls::AddrMut = stack.reserve(); - - // Reserve space for remaining pollfds - for _ in 1..nfds { - let _: reverie::syscalls::AddrMut = stack.reserve(); - } - - stack.commit()?; - - // Write kernel pollfds to guest memory - for (i, pollfd) in pollfds.iter().enumerate() { - let kernel_fd = fd_table.translate(pollfd.fd).unwrap_or(pollfd.fd); - let kernel_pollfd = PollFd { - fd: kernel_fd, - events: pollfd.events, - revents: reverie::syscalls::PollFlags::empty(), - }; - - let offset = i as isize * std::mem::size_of::() as isize; - unsafe { - guest - .memory() - .write_value(kernel_fds_addr.offset(offset), &kernel_pollfd)?; - } - } - - // Create and inject the syscall with translated FDs - let new_syscall = reverie::syscalls::Poll::new() - .with_fds(Some(kernel_fds_addr)) - .with_nfds(nfds) - .with_timeout(args.timeout()); - - let result = guest.inject(Syscall::Poll(new_syscall)).await?; - - // If the syscall failed or timed out, return early - if result <= 0 { - return Ok(Some(result)); - } - - // Read back the kernel pollfds and translate to virtual FDs - for i in 0..nfds { - let offset = i as isize * std::mem::size_of::() as isize; - let kernel_pollfd: PollFd = - unsafe { guest.memory().read_value(kernel_fds_addr.offset(offset))? }; - - // Write back the revents to the original pollfd array - let virt_pollfd = PollFd { - fd: pollfds[i as usize].fd, // Keep the virtual FD - events: pollfds[i as usize].events, - revents: kernel_pollfd.revents, - }; - - unsafe { - guest - .memory() - .write_value(fds_addr.offset(offset), &virt_pollfd)?; - } - } - - Ok(Some(result)) -} - -/// The `getdents64` system call. -/// -/// This intercepts `getdents64` system calls and translates virtual FDs to kernel FDs, -/// or calls FileOps::getdents() for virtual files. -pub async fn handle_getdents64>( - guest: &mut T, - syscall: Syscall, - args: &reverie::syscalls::Getdents64, - fd_table: &FdTable, -) -> Result { - let virtual_fd = args.fd() as i32; - - // Get the FD entry - if let Some(entry) = fd_table.get(virtual_fd) { - match entry { - FdEntry::Passthrough { kernel_fd, .. } => { - // Passthrough file - rewrite FD and return modified syscall for tail_inject - let new_syscall = args.with_fd(kernel_fd as u32); - - return Ok(crate::syscall::SyscallResult::Syscall(Syscall::Getdents64( - new_syscall, - ))); - } - FdEntry::Virtual { file_ops, .. } => { - // Virtual file - use FileOps::getdents() - match file_ops.getdents().await { - Ok(entries) => { - // Format as linux_dirent64 structures - let dirent_addr = match args.dirent() { - Some(addr) => addr, - None => { - return Ok(crate::syscall::SyscallResult::Value( - -libc::EFAULT as i64, - )) - } - }; - let count = args.count() as usize; - - let mut buf = Vec::new(); - let mut offset = 1i64; - - for (ino, name, d_type) in entries { - // Calculate record length (aligned to 8 bytes) - let name_len = name.len() + 1; // +1 for null terminator - let reclen = (19 + name_len).div_ceil(8) * 8; // 19 = sizeof(ino + off + reclen + type) - - if buf.len() + reclen > count { - break; // Not enough space - } - - // Write linux_dirent64 structure - buf.extend_from_slice(&ino.to_ne_bytes()); // d_ino (u64) - buf.extend_from_slice(&offset.to_ne_bytes()); // d_off (i64) - buf.extend_from_slice(&(reclen as u16).to_ne_bytes()); // d_reclen (u16) - buf.push(d_type); // d_type (u8) - buf.extend_from_slice(name.as_bytes()); // d_name - buf.push(0); // null terminator - - // Pad to 8-byte alignment - while buf.len() % 8 != 0 { - buf.push(0); - } - - offset += 1; - } - - // Write to guest memory - if !buf.is_empty() { - guest.memory().write_exact(dirent_addr.cast::(), &buf)?; - } - - return Ok(crate::syscall::SyscallResult::Value(buf.len() as i64)); - } - Err(_) => { - // Not a directory or error - return Ok(crate::syscall::SyscallResult::Value(-libc::ENOTDIR as i64)); - } - } - } - } - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(crate::syscall::SyscallResult::Syscall(syscall)) -} - -/// The `fstat` system call. -/// -/// This intercepts `fstat` system calls and translates virtual FDs to kernel FDs, -/// or calls FileOps::fstat() for virtual files. -pub async fn handle_fstat>( - guest: &mut T, - syscall: Syscall, - args: &reverie::syscalls::Fstat, - fd_table: &FdTable, -) -> Result { - let virtual_fd = args.fd(); - - // Get the FD entry - if let Some(entry) = fd_table.get(virtual_fd) { - match entry { - FdEntry::Passthrough { kernel_fd, .. } => { - // Passthrough file - rewrite FD and return modified syscall for tail_inject - let new_syscall = args.with_fd(kernel_fd); - - return Ok(crate::syscall::SyscallResult::Syscall(Syscall::Fstat( - new_syscall, - ))); - } - FdEntry::Virtual { file_ops, .. } => { - // Virtual file - use FileOps::fstat() - match file_ops.fstat().await { - Ok(stat_buf) => { - // Write the stat result to guest memory - if let Some(stat_addr) = args.stat() { - // Convert stat struct to bytes and write - let stat_bytes: &[u8] = unsafe { - std::slice::from_raw_parts( - &stat_buf as *const _ as *const u8, - std::mem::size_of::(), - ) - }; - guest - .memory() - .write_exact(stat_addr.0.cast::(), stat_bytes)?; - } - return Ok(crate::syscall::SyscallResult::Value(0)); // Success - } - Err(e) => { - // Map VFS errors to errno - let errno = match e { - crate::vfs::VfsError::NotFound => -libc::ENOENT as i64, - crate::vfs::VfsError::PermissionDenied => -libc::EACCES as i64, - _ => -libc::EIO as i64, - }; - return Ok(crate::syscall::SyscallResult::Value(errno)); - } - } - } - } - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(crate::syscall::SyscallResult::Syscall(syscall)) -} - -/// The `fstatat` system call. -/// -/// This intercepts `fstatat` system calls and translates virtual FDs to kernel FDs, -/// or calls FileOps::fstatat() for virtual files. -#[cfg(target_arch = "aarch64")] -pub async fn handle_fstatat>( - guest: &mut T, - syscall: Syscall, - args: &reverie::syscalls::Fstatat, - fd_table: &FdTable, - mount_table: &MountTable, -) -> Result { - if let Some(path_addr) = args.path() { - // Read the original path from guest memory - let mut path: std::path::PathBuf = path_addr.read(&guest.memory())?; - - // Handle dirfd resolution for relative paths - let dirfd = args.dirfd(); - let kernel_dirfd = if dirfd == libc::AT_FDCWD { - dirfd - } else if path.is_relative() { - // For relative paths, resolve against dirfd - if let Some(dir_entry) = fd_table.get(dirfd) { - // Check if this is a passthrough directory with a kernel FD first - if let Some(kfd) = dir_entry.kernel_fd() { - // Passthrough directory - use the kernel FD and keep path as-is - kfd - } else if let Some(dir_path) = dir_entry.path() { - // Virtual directory - resolve relative path against the directory's path - path = dir_path.join(&path); - // For virtual directories, we'll use AT_FDCWD since we have the full path now - libc::AT_FDCWD - } else { - // Virtual file without a path - this shouldn't happen for directories - - use reverie::Errno; - return Err(Error::Errno(Errno::EBADFD)); - } - } else { - // dirfd not in table - will likely fail - dirfd - } - } else { - // Absolute path - dirfd is ignored, use AT_FDCWD - libc::AT_FDCWD - }; - - // Check if this path matches a mount point - if let Some((vfs, _translated_path)) = mount_table.resolve(&path) { - // Check if this is a virtual VFS (like SQLite) - if vfs.is_virtual() { - // For virtual VFS, open the file directly without going to the kernel - match vfs.stat(&path).await { - Ok(stat_buf) => { - // Write the stat result to guest memory - if let Some(stat_addr) = args.stat() { - // Convert stat struct to bytes and write - let stat_bytes: &[u8] = unsafe { - std::slice::from_raw_parts( - &stat_buf as *const _ as *const u8, - std::mem::size_of::(), - ) - }; - guest - .memory() - .write_exact(stat_addr.0.cast::(), stat_bytes)?; - } - return Ok(crate::syscall::SyscallResult::Value(0)); // Success - } - Err(e) => { - // Map VFS errors to errno - let errno = match e { - crate::vfs::VfsError::NotFound => -libc::ENOENT as i64, - crate::vfs::VfsError::PermissionDenied => -libc::EACCES as i64, - _ => -libc::EIO as i64, - }; - return Ok(crate::syscall::SyscallResult::Value(errno)); - } - } - } - } - // No mount point matches - pass through to kernel with original path - let new_syscall = args.with_dirfd(kernel_dirfd).with_path(Some(path_addr)); - - return Ok(crate::syscall::SyscallResult::Syscall(Syscall::Fstatat( - new_syscall, - ))); - } - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(crate::syscall::SyscallResult::Syscall(syscall)) -} - -/// The `pread64` system call. -/// -/// This intercepts `pread64` system calls and translates virtual FDs to kernel FDs. -pub async fn handle_pread64>( - guest: &mut T, - args: &reverie::syscalls::Pread64, - fd_table: &FdTable, -) -> Result, Error> { - let virtual_fd = args.fd(); - - // Translate virtual FD to kernel FD - if let Some(kernel_fd) = fd_table.translate(virtual_fd) { - let new_syscall = reverie::syscalls::Pread64::new() - .with_fd(kernel_fd) - .with_buf(args.buf()) - .with_len(args.len()) - .with_offset(args.offset()); - - let result = guest.inject(Syscall::Pread64(new_syscall)).await?; - return Ok(Some(result)); - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(None) -} - -/// The `pwrite64` system call. -/// -/// This intercepts `pwrite64` system calls and translates virtual FDs to kernel FDs. -pub async fn handle_pwrite64>( - guest: &mut T, - args: &reverie::syscalls::Pwrite64, - fd_table: &FdTable, -) -> Result, Error> { - let virtual_fd = args.fd(); - - // Translate virtual FD to kernel FD - if let Some(kernel_fd) = fd_table.translate(virtual_fd) { - let new_syscall = reverie::syscalls::Pwrite64::new() - .with_fd(kernel_fd) - .with_buf(args.buf()) - .with_len(args.len()) - .with_offset(args.offset()); - - let result = guest.inject(Syscall::Pwrite64(new_syscall)).await?; - return Ok(Some(result)); - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(None) -} - -/// The `lseek` system call. -/// -/// This intercepts `lseek` system calls and translates virtual FDs to kernel FDs, -/// or calls FileOps::seek() for virtual files. -#[cfg(not(target_arch = "aarch64"))] -pub async fn handle_lseek>( - _guest: &mut T, - syscall: Syscall, - args: &reverie::syscalls::Lseek, - fd_table: &FdTable, -) -> Result { - let virtual_fd = args.fd(); - - // Get the FD entry - if let Some(entry) = fd_table.get(virtual_fd) { - match entry { - FdEntry::Passthrough { kernel_fd, .. } => { - // Passthrough file - rewrite FD and return modified syscall for tail_inject - let new_syscall = args.with_fd(kernel_fd); - - return Ok(crate::syscall::SyscallResult::Syscall(Syscall::Lseek( - new_syscall, - ))); - } - FdEntry::Virtual { file_ops, .. } => { - // Virtual file - use FileOps::seek() - // Convert Whence enum to i32 - use reverie::syscalls::Whence; - let whence = match args.whence() { - Whence::SEEK_SET => libc::SEEK_SET, - Whence::SEEK_CUR => libc::SEEK_CUR, - Whence::SEEK_END => libc::SEEK_END, - Whence::SEEK_DATA => libc::SEEK_DATA, - Whence::SEEK_HOLE => libc::SEEK_HOLE, - _ => return Ok(crate::syscall::SyscallResult::Value(-libc::EINVAL as i64)), - }; - match file_ops.seek(args.offset(), whence).await { - Ok(new_offset) => { - return Ok(crate::syscall::SyscallResult::Value(new_offset)); - } - Err(e) => { - // Map VFS errors to errno - let errno = match e { - crate::vfs::VfsError::NotFound => -libc::ENOENT as i64, - crate::vfs::VfsError::PermissionDenied => -libc::EACCES as i64, - _ => -libc::EIO as i64, - }; - return Ok(crate::syscall::SyscallResult::Value(errno)); - } - } - } - } - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(crate::syscall::SyscallResult::Syscall(syscall)) -} - -/// The `mmap` system call. -/// -/// This intercepts `mmap` system calls and translates virtual FDs to kernel FDs -/// when mapping files. Anonymous mappings (fd == -1) pass through unchanged. -pub async fn handle_mmap>( - guest: &mut T, - args: &reverie::syscalls::Mmap, - fd_table: &FdTable, -) -> Result, Error> { - let virtual_fd = args.fd(); - - // If fd is -1, it's an anonymous mapping - pass through - if virtual_fd == -1 { - return Ok(None); - } - - // Translate virtual FD to kernel FD for file-backed mappings - if let Some(kernel_fd) = fd_table.translate(virtual_fd) { - let new_syscall = reverie::syscalls::Mmap::new() - .with_addr(args.addr()) - .with_len(args.len()) - .with_prot(args.prot()) - .with_flags(args.flags()) - .with_fd(kernel_fd) - .with_offset(args.offset()); - - let result = guest.inject(Syscall::Mmap(new_syscall)).await?; - return Ok(Some(result)); - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(None) -} - -/// The `access` system call. -/// -/// This intercepts `access` system calls and translates paths according to the mount table. -#[cfg(not(target_arch = "aarch64"))] -pub async fn handle_access>( - guest: &mut T, - args: &reverie::syscalls::Access, - mount_table: &MountTable, -) -> Result, Error> { - if let Some(path_addr) = args.path() { - if let Some(new_path_addr) = translate_path(guest, path_addr, mount_table).await? { - let new_syscall = args.with_path(Some(new_path_addr)); - - return Ok(Some(Syscall::Access(new_syscall))); - } - } - Ok(None) -} - -/// The `faccessat2` system call. -/// -/// This intercepts `faccessat2` system calls, translates paths according to the mount table, -/// and virtualizes the dirfd parameter. -/// Signature: int faccessat2(int dirfd, const char *pathname, int mode, int flags); -pub async fn handle_faccessat2>( - guest: &mut T, - syscall_args: &reverie::syscalls::SyscallArgs, - mount_table: &MountTable, - fd_table: &FdTable, -) -> Result, Error> { - use reverie::syscalls::PathPtr; - - let dirfd = syscall_args.arg0 as i32; - let pathname_addr: PathPtr = unsafe { std::mem::transmute(syscall_args.arg1) }; - let mode = syscall_args.arg2 as i32; - let flags = syscall_args.arg3 as i32; - - // Check if dirfd needs virtualization - let dirfd_needs_translation = dirfd != libc::AT_FDCWD && fd_table.translate(dirfd).is_some(); - - // Check if path needs virtualization - let translated_path_opt = translate_path(guest, pathname_addr, mount_table).await?; - let path_needs_translation = translated_path_opt.is_some(); - - // If nothing needs virtualization, let the original syscall pass through - if !dirfd_needs_translation && !path_needs_translation { - return Ok(None); - } - - // Virtualize the dirfd if needed - let kernel_dirfd = if dirfd == libc::AT_FDCWD { - dirfd - } else { - fd_table.translate(dirfd).unwrap_or(dirfd) - }; - - let new_path_addr = translated_path_opt.unwrap_or(pathname_addr); - let new_path_raw: usize = unsafe { std::mem::transmute(new_path_addr) }; - - // Build and inject the syscall with virtualized parameters - let result = guest - .inject(Syscall::Other( - reverie::syscalls::Sysno::faccessat2, - reverie::syscalls::SyscallArgs { - arg0: kernel_dirfd as usize, - arg1: new_path_raw, - arg2: mode as usize, - arg3: flags as usize, - arg4: 0, - arg5: 0, - }, - )) - .await?; - - Ok(Some(result)) -} - -/// The `faccessat` system call. -/// -/// This intercepts `faccessat` system calls, translates paths according to the mount table, -/// and virtualizes the dirfd parameter. -pub async fn handle_faccessat>( - guest: &mut T, - args: &reverie::syscalls::Faccessat, - mount_table: &MountTable, - fd_table: &FdTable, -) -> Result, Error> { - let Some(pathname_addr) = args.path() else { - return Ok(None); - }; - let dirfd = args.dirfd(); - - // Check if dirfd needs virtualization - let dirfd_needs_translation = dirfd != libc::AT_FDCWD && fd_table.translate(dirfd).is_some(); - - // Check if path needs virtualization - let translated_path_opt = translate_path(guest, pathname_addr, mount_table).await?; - let path_needs_translation = translated_path_opt.is_some(); - - // If nothing needs virtualization, let the original syscall pass through - if !dirfd_needs_translation && !path_needs_translation { - return Ok(None); - } - - // Virtualize the dirfd if needed - let kernel_dirfd = if dirfd == libc::AT_FDCWD { - dirfd - } else { - fd_table.translate(dirfd).unwrap_or(dirfd) - }; - - let new_path_addr = translated_path_opt.unwrap_or(pathname_addr); - let new_syscall = - Syscall::Faccessat(args.with_dirfd(kernel_dirfd).with_path(Some(new_path_addr))); - - // Build and inject the syscall with virtualized parameters - let result = guest.inject(new_syscall).await?; - - Ok(Some(result)) -} - -/// The `rename` system call. -/// -/// This intercepts `rename` system calls and translates both paths according to the mount table. -#[cfg(not(target_arch = "aarch64"))] -pub async fn handle_rename>( - guest: &mut T, - args: &reverie::syscalls::Rename, - mount_table: &MountTable, -) -> Result, Error> { - // Only translate if we need to - otherwise pass through unchanged - let oldpath_needs_translation = args.oldpath().is_some(); - let newpath_needs_translation = args.newpath().is_some(); - - if !oldpath_needs_translation && !newpath_needs_translation { - return Ok(None); - } - - // Build new syscall with translated paths - let mut new_syscall = *args; - let mut modified = false; - - // Translate oldpath - if let Some(oldpath_addr) = args.oldpath() { - if let Some(new_path_addr) = translate_path(guest, oldpath_addr, mount_table).await? { - new_syscall = new_syscall.with_oldpath(Some(new_path_addr)); - modified = true; - } else { - new_syscall = new_syscall.with_oldpath(Some(oldpath_addr)); - } - } - - // Translate newpath - if let Some(newpath_addr) = args.newpath() { - if let Some(new_path_addr) = translate_path(guest, newpath_addr, mount_table).await? { - new_syscall = new_syscall.with_newpath(Some(new_path_addr)); - modified = true; - } else { - new_syscall = new_syscall.with_newpath(Some(newpath_addr)); - } - } - - if modified { - Ok(Some(Syscall::Rename(new_syscall))) - } else { - Ok(None) - } -} - -/// The `unlink` system call. -/// -/// This intercepts `unlink` system calls and translates paths according to the mount table. -#[cfg(not(target_arch = "aarch64"))] -pub async fn handle_unlink>( - guest: &mut T, - args: &reverie::syscalls::Unlink, - mount_table: &MountTable, -) -> Result, Error> { - if let Some(path_addr) = args.path() { - if let Some(new_path_addr) = translate_path(guest, path_addr, mount_table).await? { - let new_syscall = args.with_path(Some(new_path_addr)); - - return Ok(Some(Syscall::Unlink(new_syscall))); - } - } - Ok(None) -} - -/// The `readv` system call. -/// -/// This intercepts `readv` system calls and translates virtual FDs to kernel FDs. -pub async fn handle_readv>( - guest: &mut T, - args: &reverie::syscalls::Readv, - fd_table: &FdTable, -) -> Result, Error> { - let virtual_fd = args.fd(); - - // Translate virtual FD to kernel FD - if let Some(kernel_fd) = fd_table.translate(virtual_fd) { - let new_syscall = reverie::syscalls::Readv::new() - .with_fd(kernel_fd) - .with_iov(args.iov()); - - let result = guest.inject(Syscall::Readv(new_syscall)).await?; - return Ok(Some(result)); - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(None) -} - -/// The `writev` system call. -/// -/// This intercepts `writev` system calls and translates virtual FDs to kernel FDs. -pub async fn handle_writev>( - guest: &mut T, - args: &reverie::syscalls::Writev, - fd_table: &FdTable, -) -> Result, Error> { - let virtual_fd = args.fd(); - - // Translate virtual FD to kernel FD - if let Some(kernel_fd) = fd_table.translate(virtual_fd) { - let new_syscall = reverie::syscalls::Writev::new() - .with_fd(kernel_fd) - .with_iov(args.iov()); - - let result = guest.inject(Syscall::Writev(new_syscall)).await?; - return Ok(Some(result)); - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(None) -} - -/// The `pipe2` system call. -/// -/// This intercepts `pipe2` system calls and virtualizes the returned file descriptors. -pub async fn handle_pipe2>( - guest: &mut T, - args: &reverie::syscalls::Pipe2, - fd_table: &FdTable, -) -> Result, Error> { - use reverie::syscalls::MemoryAccess; - - // Execute the syscall to create the pipe - let result = guest.inject(Syscall::Pipe2(*args)).await?; - - // If successful, virtualize the returned FDs - if result == 0 { - // Read the kernel FDs from the pipefd array - if let Some(pipefd_addr) = args.pipefd() { - let kernel_fds: [i32; 2] = guest.memory().read_value(pipefd_addr)?; - - // Create passthrough FD entries for both pipe ends - let read_entry = FdEntry::Passthrough { - kernel_fd: kernel_fds[0], - flags: args.flags().bits(), - path: None, - }; - let write_entry = FdEntry::Passthrough { - kernel_fd: kernel_fds[1], - flags: args.flags().bits(), - path: None, - }; - - // Allocate virtual FDs for both pipe ends (pipes don't have paths) - let virtual_read_fd = fd_table.allocate(read_entry); - let virtual_write_fd = fd_table.allocate(write_entry); - - // Write each FD individually as bytes to avoid alignment issues - let read_bytes = virtual_read_fd.to_ne_bytes(); - let write_bytes = virtual_write_fd.to_ne_bytes(); - - guest - .memory() - .write_exact(pipefd_addr.cast(), &read_bytes)?; - unsafe { - guest - .memory() - .write_exact(pipefd_addr.cast::().offset(4), &write_bytes)?; - } - } - } - - Ok(Some(result)) -} - -/// The `socket` system call. -/// -/// This intercepts `socket` system calls and virtualizes the returned file descriptor. -pub async fn handle_socket>( - guest: &mut T, - args: &reverie::syscalls::Socket, - fd_table: &FdTable, -) -> Result, Error> { - // Execute the syscall to create the socket - let kernel_fd = guest.inject(Syscall::Socket(*args)).await?; - - // If the syscall succeeded (returned a valid FD), virtualize it - if kernel_fd >= 0 { - // Create passthrough FD entry (sockets don't have paths) - let entry = FdEntry::Passthrough { - kernel_fd: kernel_fd as i32, - flags: 0, - path: None, - }; - let virtual_fd = fd_table.allocate(entry); - Ok(Some(virtual_fd as i64)) - } else { - // Return the error code as-is - Ok(Some(kernel_fd)) - } -} - -/// The `sendto` system call. -/// -/// This intercepts `sendto` system calls and translates virtual FDs to kernel FDs. -pub async fn handle_sendto>( - guest: &mut T, - args: &reverie::syscalls::Sendto, - fd_table: &FdTable, -) -> Result, Error> { - let virtual_fd = args.fd(); - - // Translate virtual FD to kernel FD - if let Some(kernel_fd) = fd_table.translate(virtual_fd) { - let new_syscall = reverie::syscalls::Sendto::new() - .with_fd(kernel_fd) - .with_buf(args.buf()) - .with_flags(args.flags()) - .with_addr(args.addr()); - - let result = guest.inject(Syscall::Sendto(new_syscall)).await?; - return Ok(Some(result)); - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(None) -} - -/// The `connect` system call. -/// -/// This intercepts `connect` system calls and translates virtual FDs to kernel FDs. -pub async fn handle_connect>( - guest: &mut T, - args: &reverie::syscalls::Connect, - fd_table: &FdTable, -) -> Result, Error> { - let virtual_fd = args.fd(); - - // Translate virtual FD to kernel FD - if let Some(kernel_fd) = fd_table.translate(virtual_fd) { - let new_syscall = reverie::syscalls::Connect::new() - .with_fd(kernel_fd) - .with_addrlen(args.addrlen()); - - let result = guest.inject(Syscall::Connect(new_syscall)).await?; - return Ok(Some(result)); - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(None) -} - -/// The `getpeername` system call. -/// -/// This intercepts `getpeername` system calls and translates virtual FDs to kernel FDs. -pub async fn handle_getpeername>( - guest: &mut T, - args: &reverie::syscalls::Getpeername, - fd_table: &FdTable, -) -> Result, Error> { - let virtual_fd = args.fd(); - - // Translate virtual FD to kernel FD - if let Some(kernel_fd) = fd_table.translate(virtual_fd) { - let new_syscall = reverie::syscalls::Getpeername::new() - .with_fd(kernel_fd) - .with_usockaddr(args.usockaddr()) - .with_usockaddr_len(args.usockaddr_len()); - - let result = guest.inject(Syscall::Getpeername(new_syscall)).await?; - return Ok(Some(result)); - } - - // FD not in table, let the original syscall through (will likely fail with EBADF) - Ok(None) -} - -/// The `chdir` system call. -/// -/// This intercepts `chdir` system calls and translates paths according to the mount table. -pub async fn handle_chdir>( - guest: &mut T, - args: &reverie::syscalls::Chdir, - mount_table: &MountTable, -) -> Result, Error> { - if let Some(path_addr) = args.path() { - if let Some(new_path_addr) = translate_path(guest, path_addr, mount_table).await? { - let new_syscall = args.with_path(Some(new_path_addr)); - - return Ok(Some(Syscall::Chdir(new_syscall))); - } - } - Ok(None) -} - -/// The `rmdir` system call. -/// -/// This intercepts `rmdir` system calls and translates paths according to the mount table. -#[cfg(not(target_arch = "aarch64"))] -pub async fn handle_rmdir>( - guest: &mut T, - args: &reverie::syscalls::Rmdir, - mount_table: &MountTable, -) -> Result, Error> { - if let Some(path_addr) = args.path() { - if let Some(new_path_addr) = translate_path(guest, path_addr, mount_table).await? { - let new_syscall = args.with_path(Some(new_path_addr)); - return Ok(Some(Syscall::Rmdir(new_syscall))); - } - } - Ok(None) -} - -/// The `unlinkat` system call (used for `unlink` and `rmdir` on ARM). -/// -/// This intercepts `unlinkat` system calls and translates paths according to the mount table. -/// Signature: int unlinkat(int dirfd, const char *pathname, int flags); -/// Note: On ARM (aarch64), both unlink and rmdir are implemented via unlinkat: -/// - unlink: unlinkat(AT_FDCWD, pathname, 0) -/// - rmdir: unlinkat(AT_FDCWD, pathname, AT_REMOVEDIR) -#[cfg(target_arch = "aarch64")] -pub async fn handle_unlinkat>( - guest: &mut T, - args: &reverie::syscalls::Unlinkat, - mount_table: &MountTable, -) -> Result, Error> { - use libc::AT_FDCWD; - - let dirfd = args.dirfd(); - let flags = args.flags(); - - // Only handle AT_FDCWD for now (same as handle_chmod) - if dirfd != AT_FDCWD { - return Ok(None); - } - - if let Some(path_addr) = args.path() { - if let Some(new_path_addr) = translate_path(guest, path_addr, mount_table).await? { - let new_syscall = args - .with_dirfd(AT_FDCWD) - .with_path(Some(new_path_addr)) - .with_flags(flags); - return Ok(Some(Syscall::Unlinkat(new_syscall))); - } - } - Ok(None) -} - -/// The `fchmodat` system call (used for `chmod` on ARM). -/// -/// This intercepts `fchmodat` system calls and translates paths according to the mount table. -/// Signature: int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags); -/// Note: On ARM (aarch64), chmod is implemented via fchmodat with AT_FDCWD. -pub async fn handle_chmod>( - guest: &mut T, - syscall_args: &reverie::syscalls::SyscallArgs, - mount_table: &MountTable, -) -> Result, Error> { - use libc::AT_FDCWD; - use reverie::syscalls::{AtFlags, Mode, PathPtr, Syscall}; - - let dirfd = syscall_args.arg0 as i32; - let pathname_addr = match PathPtr::from_ptr(syscall_args.arg1 as _) { - Some(ptr) => ptr, - None => { - return Ok(None); - } - }; - let mode = syscall_args.arg2 as u32; - let flags = syscall_args.arg3 as i32; - - if dirfd != libc::AT_FDCWD { - return Ok(None); - } - - if let Some(new_path_addr) = translate_path(guest, pathname_addr, mount_table).await? { - let new_syscall = reverie::syscalls::Fchmodat::new() - .with_dirfd(AT_FDCWD) - .with_path(Some(new_path_addr)) - .with_mode(Mode::from_bits_truncate(mode)) - .with_flags(AtFlags::from_bits_truncate(flags)); - let injected = guest.inject(Syscall::Fchmodat(new_syscall)).await?; - - Ok(Some(injected)) - } else { - Ok(None) - } -} - -/// The `fchownat` system call. -/// -/// This intercepts `fchownat` system calls, translates paths according to the mount table, -/// and virtualizes the dirfd parameter. -pub async fn handle_fchownat>( - guest: &mut T, - args: &reverie::syscalls::Fchownat, - mount_table: &MountTable, - fd_table: &FdTable, -) -> Result, Error> { - let Some(pathname_addr) = args.path() else { - return Ok(None); - }; - let dirfd = args.dirfd(); - - // Check if dirfd needs virtualization - let dirfd_needs_translation = dirfd != libc::AT_FDCWD && fd_table.translate(dirfd).is_some(); - - // Check if path needs virtualization - let translated_path_opt = translate_path(guest, pathname_addr, mount_table).await?; - let path_needs_translation = translated_path_opt.is_some(); - - // If nothing needs virtualization, let the original syscall pass through - if !dirfd_needs_translation && !path_needs_translation { - return Ok(None); - } - - // Virtualize the dirfd if needed - let kernel_dirfd = if dirfd == libc::AT_FDCWD { - dirfd - } else { - fd_table.translate(dirfd).unwrap_or(dirfd) - }; - - let new_path_addr = translated_path_opt.unwrap_or(pathname_addr); - let new_syscall = - Syscall::Fchownat(args.with_dirfd(kernel_dirfd).with_path(Some(new_path_addr))); - - // Build and inject the syscall with virtualized parameters - let result = guest.inject(new_syscall).await?; - - Ok(Some(result)) -} diff --git a/sandbox/src/syscall/mod.rs b/sandbox/src/syscall/mod.rs deleted file mode 100644 index f2177bc2..00000000 --- a/sandbox/src/syscall/mod.rs +++ /dev/null @@ -1,517 +0,0 @@ -pub mod file; -pub mod process; -pub mod stat; -pub mod xattr; - -use crate::{ - sandbox::Sandbox, - vfs::{fdtable::FdTable, mount::MountTable}, -}; -use reverie::{ - syscalls::{MemoryAccess, PathPtr, ReadAddr, Syscall}, - Error, Guest, Stack, -}; -use std::{ffi::CString, path::PathBuf}; - -/// Common path translation logic for syscalls. -/// -/// This function encapsulates the common pattern used by all path-based syscall -/// handlers. It performs the following operations: -/// -/// 1. Reads the original path from guest memory -/// 2. Resolves the path through the mount table to get the translated host path -/// 3. Converts the translated path to a C string -/// 4. Allocates space on the guest stack for the new path -/// 5. Writes the translated path to guest memory -/// 6. Returns the new path address for use in the modified syscall -/// -/// # Arguments -/// * `guest` - The guest process being traced -/// * `path_addr` - The address of the original path in guest memory -/// * `mount_table` - The mount table for path resolution -/// -/// # Returns -/// * `Ok(Some(addr))` - Path was successfully translated, returns new address -/// * `Ok(None)` - Path does not need translation (no matching mount point) -/// * `Err(e)` - An error occurred during translation -/// -/// # Safety -/// This function allocates memory on the guest stack and writes to guest memory. -/// The allocated memory is automatically cleaned up when the syscall returns, -/// as the guest process unwinds its own stack frame. -pub(crate) async fn translate_path<'a, T: Guest>( - guest: &'a mut T, - path_addr: PathPtr<'a>, - mount_table: &MountTable, -) -> Result>, Error> { - // Read the original path from guest memory - let path: PathBuf = path_addr.read(&guest.memory())?; - - // Only process valid UTF-8 paths - if path.to_str().is_none() { - return Ok(None); - } - - // Resolve through mount table to get the translated host path - let (_vfs, translated_path) = match mount_table.resolve(&path) { - Some(result) => result, - None => return Ok(None), // No mount point matches, use original path - }; - - // Convert translated path to a C string for the syscall - let new_path_str = translated_path.to_string_lossy().to_string(); - let new_path_cstr = CString::new(new_path_str).map_err(|_| reverie::syscalls::Errno::EINVAL)?; - - // Allocate space on the guest stack and write the new path - let bytes = new_path_cstr.as_bytes_with_nul(); - let mut stack = guest.stack().await; - let addr: reverie::syscalls::AddrMut = stack.reserve(); - stack.commit()?; - - let byte_addr = addr.cast::(); - guest.memory().write_exact(byte_addr, bytes)?; - - // SAFETY: The transmute converts AddrMut to PathPtr<'a>. - // This is safe because: - // 1. We just allocated `byte_addr` from the guest stack via stack.reserve() - // 2. We wrote a valid null-terminated C string to this address - // 3. Reverie treats these pointer types as thin wrappers around raw pointers - // 4. PathPtr is a newtype around CStrPtr, which is compatible with a char* pointer - // 5. The guest will read this as a const char* pointer for the syscall path argument - Ok(Some(unsafe { - std::mem::transmute::, reverie::syscalls::PathPtr<'_>>( - byte_addr, - ) - })) -} - -/// System call dispatch. -/// -/// This function dispatches a system call to the appropriate handler if the -/// system call needs to be intercepted. -/// -/// Returns either: -/// - `SyscallResult::Value(i64)` if the handler executed the syscall and has a return value -/// - `SyscallResult::Syscall(Syscall)` if a syscall should be injected (either modified or original) -pub async fn dispatch_syscall>( - guest: &mut T, - syscall: Syscall, - mount_table: &MountTable, - fd_table: &FdTable, -) -> Result { - // FIXME: We need to intercept all system calls that use a path or file descriptor. - match &syscall { - Syscall::Openat(args) => { - if let Some(result) = file::handle_openat(guest, args, mount_table, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Read(args) => file::handle_read(guest, syscall, args, fd_table).await, - Syscall::Write(args) => file::handle_write(guest, syscall, args, fd_table).await, - Syscall::Close(args) => file::handle_close(guest, syscall, args, fd_table).await, - Syscall::Dup(args) => { - if let Some(result) = file::handle_dup(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - #[cfg(not(target_arch = "aarch64"))] - Syscall::Dup2(args) => { - if let Some(result) = file::handle_dup2(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Dup3(args) => { - if let Some(result) = file::handle_dup3(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - #[cfg(not(target_arch = "aarch64"))] - Syscall::Rmdir(args) => { - if let Some(modified) = file::handle_rmdir(guest, args, mount_table).await? { - Ok(SyscallResult::Syscall(modified)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - #[cfg(target_arch = "aarch64")] - Syscall::Unlinkat(args) => { - if let Some(modified) = file::handle_unlinkat(guest, args, mount_table).await? { - Ok(SyscallResult::Syscall(modified)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - #[cfg(not(target_arch = "aarch64"))] - Syscall::Fork(args) => { - if let Some(result) = process::handle_fork(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - #[cfg(not(target_arch = "aarch64"))] - Syscall::Vfork(args) => { - if let Some(result) = process::handle_vfork(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Clone(args) => { - if let Some(result) = process::handle_clone(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Clone3(args) => { - if let Some(result) = process::handle_clone3(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Statx(args) => { - if let Some(result) = stat::handle_statx(guest, args, mount_table, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - #[cfg(not(target_arch = "aarch64"))] - Syscall::Newfstatat(args) => { - if let Some(result) = - stat::handle_newfstatat(guest, args, mount_table, fd_table).await? - { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Statfs(args) => { - if let Some(modified) = stat::handle_statfs(guest, args, mount_table).await? { - Ok(SyscallResult::Syscall(modified)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - #[cfg(not(target_arch = "aarch64"))] - Syscall::Readlink(args) => { - if let Some(result) = stat::handle_readlink(guest, args, mount_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Readlinkat(args) => { - if let Some(result) = - stat::handle_readlinkat(guest, args, mount_table, fd_table).await? - { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - #[cfg(not(target_arch = "aarch64"))] - Syscall::Symlink(args) => { - if let Some(result) = stat::handle_symlink(guest, args, mount_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Symlinkat(args) => { - if let Some(result) = stat::handle_symlinkat(guest, args, mount_table, fd_table).await? - { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Linkat(args) => { - if let Some(result) = stat::handle_linkat(guest, args, mount_table, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Llistxattr(args) => { - if let Some(modified) = xattr::handle_llistxattr(guest, args, mount_table).await? { - Ok(SyscallResult::Syscall(modified)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Lgetxattr(args) => { - if let Some(modified) = xattr::handle_lgetxattr(guest, args, mount_table).await? { - Ok(SyscallResult::Syscall(modified)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Ioctl(args) => { - if let Some(result) = file::handle_ioctl(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Fcntl(args) => { - if let Some(result) = file::handle_fcntl(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Pselect6(args) => { - if let Some(result) = file::handle_pselect6(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - #[cfg(not(target_arch = "aarch64"))] - Syscall::Poll(args) => { - if let Some(result) = file::handle_poll(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Getdents64(args) => file::handle_getdents64(guest, syscall, args, fd_table).await, - Syscall::Fstat(args) => file::handle_fstat(guest, syscall, args, fd_table).await, - #[cfg(target_arch = "aarch64")] - Syscall::Fstatat(args) => { - file::handle_fstatat(guest, syscall, args, fd_table, mount_table).await - } - Syscall::Pread64(args) => { - if let Some(result) = file::handle_pread64(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Pwrite64(args) => { - if let Some(result) = file::handle_pwrite64(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - #[cfg(not(target_arch = "aarch64"))] - Syscall::Lseek(args) => file::handle_lseek(guest, syscall, args, fd_table).await, - Syscall::Readv(args) => { - if let Some(result) = file::handle_readv(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Writev(args) => { - if let Some(result) = file::handle_writev(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Pipe2(args) => { - if let Some(result) = file::handle_pipe2(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Socket(args) => { - if let Some(result) = file::handle_socket(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Sendto(args) => { - if let Some(result) = file::handle_sendto(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Connect(args) => { - if let Some(result) = file::handle_connect(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Getpeername(args) => { - if let Some(result) = file::handle_getpeername(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - // Signal-related syscalls - passthrough (no fd/path interception needed) - Syscall::RtSigaction(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::RtSigprocmask(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::RtSigreturn(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Sigaltstack(_) => Ok(SyscallResult::Syscall(syscall)), - // Process execution and termination - passthrough - Syscall::Execve(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Execveat(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Exit(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::ExitGroup(_) => Ok(SyscallResult::Syscall(syscall)), - // Process information - passthrough - Syscall::Getpid(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Getppid(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Gettid(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Getuid(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Geteuid(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Getgid(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Getegid(_) => Ok(SyscallResult::Syscall(syscall)), - // Wait syscalls - passthrough - Syscall::Wait4(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Waitid(_) => Ok(SyscallResult::Syscall(syscall)), - // Memory management - Syscall::Brk(_) => Ok(SyscallResult::Syscall(syscall)), - #[cfg(not(target_arch = "aarch64"))] - Syscall::ArchPrctl(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Mmap(args) => { - if let Some(result) = file::handle_mmap(guest, args, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Munmap(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Mprotect(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Mremap(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Madvise(_) => Ok(SyscallResult::Syscall(syscall)), - // Path-based file operations - #[cfg(not(target_arch = "aarch64"))] - Syscall::Access(args) => { - if let Some(modified) = file::handle_access(guest, args, mount_table).await? { - Ok(SyscallResult::Syscall(modified)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Faccessat(args) => { - if let Some(result) = file::handle_faccessat(guest, args, mount_table, fd_table).await? - { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - #[cfg(not(target_arch = "aarch64"))] - Syscall::Rename(args) => { - if let Some(modified) = file::handle_rename(guest, args, mount_table).await? { - Ok(SyscallResult::Syscall(modified)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - #[cfg(not(target_arch = "aarch64"))] - Syscall::Unlink(args) => { - if let Some(modified) = file::handle_unlink(guest, args, mount_table).await? { - Ok(SyscallResult::Syscall(modified)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Chdir(args) => { - if let Some(modified) = file::handle_chdir(guest, args, mount_table).await? { - Ok(SyscallResult::Syscall(modified)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Syscall::Fchownat(args) => { - if let Some(result) = file::handle_fchownat(guest, args, mount_table, fd_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - // Threading and synchronization - passthrough - Syscall::SetTidAddress(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::SetRobustList(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Futex(_) => Ok(SyscallResult::Syscall(syscall)), - // Time - passthrough - #[cfg(not(target_arch = "aarch64"))] - Syscall::Time(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::ClockGettime(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::ClockGetres(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Gettimeofday(_) => Ok(SyscallResult::Syscall(syscall)), - // Random - passthrough - Syscall::Getrandom(_) => Ok(SyscallResult::Syscall(syscall)), - // Resource limits - passthrough - Syscall::Prlimit64(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Getrlimit(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Setrlimit(_) => Ok(SyscallResult::Syscall(syscall)), - // Signals - passthrough - Syscall::Tgkill(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Tkill(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Kill(_) => Ok(SyscallResult::Syscall(syscall)), - // System information - passthrough - Syscall::Uname(_) => Ok(SyscallResult::Syscall(syscall)), - #[cfg(not(target_arch = "aarch64"))] - Syscall::Getpgrp(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Getpgid(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Setpgid(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Setsid(_) => Ok(SyscallResult::Syscall(syscall)), - // Permission management - passthrough - Syscall::Setfsuid(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Setfsgid(_) => Ok(SyscallResult::Syscall(syscall)), - Syscall::Umask(_) => Ok(SyscallResult::Syscall(syscall)), - // Process control - passthrough - Syscall::Prctl(_) => Ok(SyscallResult::Syscall(syscall)), - // Handle specific "Other" syscalls by syscall number - Syscall::Other(num, args) => { - use reverie::syscalls::Sysno; - match *num { - Sysno::rseq => Ok(SyscallResult::Syscall(syscall)), // rseq - passthrough - Sysno::lseek => Ok(SyscallResult::Syscall(syscall)), - Sysno::fchmodat => { - if let Some(result) = file::handle_chmod(guest, args, mount_table).await? { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - Sysno::faccessat2 => { - if let Some(result) = - file::handle_faccessat2(guest, args, mount_table, fd_table).await? - { - Ok(SyscallResult::Value(result)) - } else { - Ok(SyscallResult::Syscall(syscall)) - } - } - _ => { - eprintln!("WARNING: Unsupported syscall: {:?}", num); - Err(Error::Errno(reverie::syscalls::Errno::ENOSYS)) - } - } - } - _ => { - eprintln!("WARNING: Unsupported syscall: {:?}", syscall); - Err(Error::Errno(reverie::syscalls::Errno::ENOSYS)) - } - } -} - -/// Result of a syscall handler -pub enum SyscallResult { - /// Handler executed the syscall and returned a value - Value(i64), - /// Handler modified the syscall, which should be tail-injected - Syscall(Syscall), -} diff --git a/sandbox/src/syscall/process.rs b/sandbox/src/syscall/process.rs deleted file mode 100644 index 17c472df..00000000 --- a/sandbox/src/syscall/process.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::{sandbox, sandbox::Sandbox, vfs::fdtable::FdTable}; -use reverie::{syscalls::Syscall, Error, Guest}; - -/// The `fork` system call. -/// -/// This intercepts `fork` system calls to properly handle FD table inheritance. -/// The child process gets a deep copy of the parent's FD table. -#[cfg(not(target_arch = "aarch64"))] -pub async fn handle_fork>( - guest: &mut T, - _args: &reverie::syscalls::Fork, - parent_fd_table: &FdTable, -) -> Result, Error> { - // Execute the fork syscall - let result = guest - .inject(Syscall::Fork(reverie::syscalls::Fork::new())) - .await?; - - if result > 0 { - // We are in the parent process - result is the child PID - // Create a deep copy of our FD table for the child - let child_fd_table = parent_fd_table.deep_clone(); - sandbox::insert_fd_table(result as i32, child_fd_table); - } - // If result == 0, we're in the child - the FD table was already set up by the parent - // If result < 0, fork failed - no action needed - - Ok(Some(result)) -} - -/// The `vfork` system call. -/// -/// This intercepts `vfork` system calls. vfork shares the address space with the parent -/// until exec, so we share the FD table (shallow copy) rather than deep copying. -/// -/// Note: In practice, modern Linux kernels implement vfork identically to fork with -/// copy-on-write, so we treat it the same as fork for FD table purposes. -#[cfg(not(target_arch = "aarch64"))] -pub async fn handle_vfork>( - guest: &mut T, - _args: &reverie::syscalls::Vfork, - parent_fd_table: &FdTable, -) -> Result, Error> { - // Execute the vfork syscall - let result = guest - .inject(Syscall::Vfork(reverie::syscalls::Vfork::new())) - .await?; - - if result > 0 { - // Parent process - create a deep copy for the child - // (Even though vfork semantics suggest sharing, it's safer to deep copy - // since the child will exec or exit, and we need independent FD tracking) - let child_fd_table = parent_fd_table.deep_clone(); - sandbox::insert_fd_table(result as i32, child_fd_table); - } - - Ok(Some(result)) -} - -/// The `clone` system call. -/// -/// This intercepts `clone` system calls. Clone behavior depends on flags: -/// - CLONE_FILES: child shares FD table with parent (shallow copy) -/// - No CLONE_FILES: child gets independent FD table (deep copy, like fork) -/// -/// Note: CLONE_FILES is typically used for threads, while process clones -/// without CLONE_FILES behave like fork. -pub async fn handle_clone>( - guest: &mut T, - args: &reverie::syscalls::Clone, - parent_fd_table: &FdTable, -) -> Result, Error> { - // Execute the clone syscall - let result = guest.inject(Syscall::Clone(*args)).await?; - - if result > 0 { - // We are in the parent process - result is the child PID/TID - // Check if CLONE_FILES flag is set (0x00000400) - const CLONE_FILES: i32 = 0x00000400; - - let flags = args.flags(); - let share_fds = flags.bits() & CLONE_FILES != 0; - - if share_fds { - // CLONE_FILES set - share the FD table (shallow copy) - // Both parent and child will see the same FdTable Arc - sandbox::insert_fd_table(result as i32, parent_fd_table.clone()); - } else { - // CLONE_FILES not set - create independent FD table (deep copy) - let child_fd_table = parent_fd_table.deep_clone(); - sandbox::insert_fd_table(result as i32, child_fd_table); - } - } - // If result == 0, we're in the child - FD table already set up by parent - // If result < 0, clone failed - - Ok(Some(result)) -} - -/// The `clone3` system call. -/// -/// This is the modern clone interface. We need to parse the clone_args structure -/// to determine the flags. -pub async fn handle_clone3>( - guest: &mut T, - args: &reverie::syscalls::Clone3, - parent_fd_table: &FdTable, -) -> Result, Error> { - // Execute the clone3 syscall - let result = guest.inject(Syscall::Clone3(*args)).await?; - - if result > 0 { - // Parent process - result is child PID/TID - // For clone3, we'd need to read the clone_args structure from memory - // to get the flags. For now, we default to deep copy (safer). - // TODO: Parse clone_args to check CLONE_FILES flag - let child_fd_table = parent_fd_table.deep_clone(); - sandbox::insert_fd_table(result as i32, child_fd_table); - } - - Ok(Some(result)) -} diff --git a/sandbox/src/syscall/stat.rs b/sandbox/src/syscall/stat.rs deleted file mode 100644 index 9a1e6066..00000000 --- a/sandbox/src/syscall/stat.rs +++ /dev/null @@ -1,550 +0,0 @@ -use crate::{ - sandbox::Sandbox, - syscall::translate_path, - vfs::{fdtable::FdTable, mount::MountTable}, -}; -use reverie::{ - syscalls::{MemoryAccess, ReadAddr, Syscall}, - Error, Guest, Stack, -}; - -/// The `statx` system call. -/// -/// This intercepts `statx` system calls and translates paths according to the mount table -/// and virtualizes the dirfd. -/// Returns `Some(result)` if the syscall was handled and the result should be returned directly, -/// or `None` if the original syscall should be used. -pub async fn handle_statx>( - guest: &mut T, - args: &reverie::syscalls::Statx, - mount_table: &MountTable, - fd_table: &FdTable, -) -> Result, Error> { - let dirfd = args.dirfd(); - // AT_FDCWD is -100 - let kernel_dirfd = if dirfd == -100 { - dirfd - } else { - fd_table.translate(dirfd).unwrap_or(dirfd) - }; - - if let Some(path_addr) = args.path() { - // Read the original path from guest memory - let path: std::path::PathBuf = path_addr.read(&guest.memory())?; - - // Check if this path matches a mount point - if let Some((vfs, _translated_path)) = mount_table.resolve(&path) { - // Check if this is a virtual VFS (like SQLite) - if vfs.is_virtual() { - // For virtual VFS, statx is not supported - return ENOSYS - // The caller will fall back to newfstatat - return Ok(Some(-libc::ENOSYS as i64)); - } - } - - if let Some(new_path_addr) = translate_path(guest, path_addr, mount_table).await? { - let new_syscall = reverie::syscalls::Statx::new() - .with_dirfd(kernel_dirfd) - .with_path(Some(new_path_addr)) - .with_flags(args.flags()) - .with_mask(args.mask()) - .with_statx(args.statx()); - - let result = guest.inject(Syscall::Statx(new_syscall)).await?; - return Ok(Some(result)); - } - } - Ok(None) -} - -/// The `newfstatat` system call. -/// -/// This intercepts `newfstatat` system calls and translates paths according to the mount table -/// and virtualizes the dirfd. -/// Returns `Some(result)` if the syscall was handled and the result should be returned directly, -/// or `None` if the original syscall should be used. -#[cfg(not(target_arch = "aarch64"))] -pub async fn handle_newfstatat>( - guest: &mut T, - args: &reverie::syscalls::Newfstatat, - mount_table: &MountTable, - fd_table: &FdTable, -) -> Result, Error> { - use reverie::syscalls::AtFlags; - let dirfd = args.dirfd(); - // AT_FDCWD is -100 - let kernel_dirfd = if dirfd == -100 { - dirfd - } else { - fd_table.translate(dirfd).unwrap_or(dirfd) - }; - - if let Some(path_addr) = args.path() { - // Read the original path from guest memory - let path: std::path::PathBuf = path_addr.read(&guest.memory())?; - - // Check if this path matches a mount point - if let Some((vfs, _translated_path)) = mount_table.resolve(&path) { - // Check if this is a virtual VFS (like SQLite) - if vfs.is_virtual() { - let flags = args.flags(); - let follow_symlinks = !flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW); - - let stat_result = if follow_symlinks { - vfs.stat(&path).await - } else { - vfs.lstat(&path).await - }; - - match stat_result { - Ok(stat_buf) => { - // Write the stat result to guest memory - if let Some(stat_addr) = args.stat() { - // Convert stat struct to bytes and write - let stat_bytes: &[u8] = unsafe { - std::slice::from_raw_parts( - &stat_buf as *const _ as *const u8, - std::mem::size_of::(), - ) - }; - guest - .memory() - .write_exact(stat_addr.0.cast::(), stat_bytes)?; - } - return Ok(Some(0)); // Success - } - Err(e) => { - // Map VFS errors to errno - let errno = match e { - crate::vfs::VfsError::NotFound => -libc::ENOENT as i64, - crate::vfs::VfsError::PermissionDenied => -libc::EACCES as i64, - _ => -libc::EIO as i64, - }; - return Ok(Some(errno)); - } - } - } - } - - if let Some(new_path_addr) = translate_path(guest, path_addr, mount_table).await? { - let new_syscall = reverie::syscalls::Newfstatat::new() - .with_dirfd(kernel_dirfd) - .with_path(Some(new_path_addr)) - .with_stat(args.stat()) - .with_flags(args.flags()); - - let result = guest.inject(Syscall::Newfstatat(new_syscall)).await?; - return Ok(Some(result)); - } - } - Ok(None) -} - -/// The `statfs` system call. -/// -/// This intercepts `statfs` system calls and translates paths according to the mount table. -pub async fn handle_statfs>( - guest: &mut T, - args: &reverie::syscalls::Statfs, - mount_table: &MountTable, -) -> Result, Error> { - if let Some(path_addr) = args.path() { - if let Some(new_path_addr) = translate_path(guest, path_addr, mount_table).await? { - let new_syscall = args.with_path(Some(new_path_addr)); - - return Ok(Some(Syscall::Statfs(new_syscall))); - } - } - Ok(None) -} - -/// The `readlink` system call. -/// -/// This intercepts `readlink` system calls and translates paths according to the mount table. -#[cfg(not(target_arch = "aarch64"))] -pub async fn handle_readlink>( - guest: &mut T, - args: &reverie::syscalls::Readlink, - mount_table: &MountTable, -) -> Result, Error> { - if let Some(path_addr) = args.path() { - let path: std::path::PathBuf = path_addr.read(&guest.memory())?; - - // Check if this path matches a mount point - if let Some((vfs, _translated_path)) = mount_table.resolve(&path) { - // Check if this is a virtual VFS (like SQLite) - if vfs.is_virtual() { - // Call VFS readlink method directly - match vfs.readlink(&path).await { - Ok(target) => { - // Write the target to the user's buffer - if let Some(buf_addr) = args.buf() { - let bufsize = args.bufsize(); - let target_str = target.to_string_lossy(); - let target_bytes = target_str.as_bytes(); - let bytes_to_write = std::cmp::min(target_bytes.len(), bufsize); - - guest.memory().write_exact( - buf_addr.cast::(), - &target_bytes[..bytes_to_write], - )?; - - return Ok(Some(bytes_to_write as i64)); - } - return Ok(Some(0)); - } - Err(e) => { - // Map VFS errors to errno - let errno = match e { - crate::vfs::VfsError::NotFound => -libc::ENOENT as i64, - crate::vfs::VfsError::PermissionDenied => -libc::EACCES as i64, - _ => -libc::EINVAL as i64, - }; - return Ok(Some(errno)); - } - } - } - } - - if let Some(new_path_addr) = translate_path(guest, path_addr, mount_table).await? { - let new_syscall = reverie::syscalls::Readlink::new() - .with_path(Some(new_path_addr)) - .with_buf(args.buf()) - .with_bufsize(args.bufsize()); - - let result = guest.inject(Syscall::Readlink(new_syscall)).await?; - return Ok(Some(result)); - } - } - Ok(None) -} - -/// The `readlinkat` system call. -/// -/// This intercepts `readlinkat` system calls and translates paths according to the mount table -/// and virtualizes the dirfd. -pub async fn handle_readlinkat>( - guest: &mut T, - args: &reverie::syscalls::Readlinkat, - mount_table: &MountTable, - fd_table: &FdTable, -) -> Result, Error> { - let dirfd = args.dirfd(); - // AT_FDCWD is -100 - let kernel_dirfd = if dirfd == -100 { - dirfd - } else { - fd_table.translate(dirfd).unwrap_or(dirfd) - }; - - if let Some(path_addr) = args.path() { - let path: std::path::PathBuf = path_addr.read(&guest.memory())?; - - // Check if this path matches a mount point - if let Some((vfs, _translated_path)) = mount_table.resolve(&path) { - // Check if this is a virtual VFS (like SQLite) - if vfs.is_virtual() { - // Call VFS readlink method directly - match vfs.readlink(&path).await { - Ok(target) => { - // Write the target to the user's buffer - if let Some(buf_addr) = args.buf() { - let bufsize = args.buf_len(); - let target_str = target.to_string_lossy(); - let target_bytes = target_str.as_bytes(); - let bytes_to_write = std::cmp::min(target_bytes.len(), bufsize); - - guest.memory().write_exact( - buf_addr.cast::(), - &target_bytes[..bytes_to_write], - )?; - - return Ok(Some(bytes_to_write as i64)); - } - return Ok(Some(0)); - } - Err(e) => { - // Map VFS errors to errno - let errno = match e { - crate::vfs::VfsError::NotFound => -libc::ENOENT as i64, - crate::vfs::VfsError::PermissionDenied => -libc::EACCES as i64, - _ => -libc::EINVAL as i64, - }; - return Ok(Some(errno)); - } - } - } - } - - if let Some(new_path_addr) = translate_path(guest, path_addr, mount_table).await? { - let new_syscall = reverie::syscalls::Readlinkat::new() - .with_dirfd(kernel_dirfd) - .with_path(Some(new_path_addr)) - .with_buf(args.buf()) - .with_buf_len(args.buf_len()); - - let result = guest.inject(Syscall::Readlinkat(new_syscall)).await?; - return Ok(Some(result)); - } - } - Ok(None) -} - -/// The `symlink` system call. -/// -/// This intercepts `symlink` system calls and translates the linkpath according to the mount table. -/// The target path is left as-is since it's just a string stored in the symlink. -/// Returns `Some(result)` if the syscall was handled and the result should be returned directly, -/// or `None` if the original syscall should be used. -#[cfg(not(target_arch = "aarch64"))] -pub async fn handle_symlink>( - guest: &mut T, - args: &reverie::syscalls::Symlink, - mount_table: &MountTable, -) -> Result, Error> { - // Read the linkpath from guest memory - if let Some(linkpath_addr) = args.linkpath() { - let linkpath: std::path::PathBuf = linkpath_addr.read(&guest.memory())?; - - // Read the target from guest memory - if let Some(target_addr) = args.target() { - let target: std::path::PathBuf = target_addr.read(&guest.memory())?; - - // Check if this path matches a mount point - if let Some((vfs, _translated_path)) = mount_table.resolve(&linkpath) { - // Check if this is a virtual VFS (like SQLite) - if vfs.is_virtual() { - // Call VFS symlink method directly - match vfs.symlink(&target, &linkpath).await { - Ok(()) => return Ok(Some(0)), // Success - Err(e) => { - // Map VFS errors to errno - let errno = match e { - crate::vfs::VfsError::NotFound => -libc::ENOENT as i64, - crate::vfs::VfsError::PermissionDenied => -libc::EACCES as i64, - crate::vfs::VfsError::AlreadyExists => -libc::EEXIST as i64, - _ => -libc::EIO as i64, - }; - return Ok(Some(errno)); - } - } - } - } - - if let Some(new_linkpath_addr) = - translate_path(guest, linkpath_addr, mount_table).await? - { - let new_syscall = reverie::syscalls::Symlink::new() - .with_target(args.target()) - .with_linkpath(Some(new_linkpath_addr)); - - let result = guest.inject(Syscall::Symlink(new_syscall)).await?; - return Ok(Some(result)); - } - } - } - Ok(None) -} - -/// The `symlinkat` system call. -/// -/// This intercepts `symlinkat` system calls and translates the linkpath according to the mount table -/// and virtualizes the dirfd. -/// The target path is left as-is since it's just a string stored in the symlink. -/// Returns `Some(result)` if the syscall was handled and the result should be returned directly, -/// or `None` if the original syscall should be used. -pub async fn handle_symlinkat>( - guest: &mut T, - args: &reverie::syscalls::Symlinkat, - mount_table: &MountTable, - fd_table: &FdTable, -) -> Result, Error> { - let dirfd = args.newdirfd(); - // AT_FDCWD is -100 - let kernel_dirfd = if dirfd == -100 { - dirfd - } else { - fd_table.translate(dirfd).unwrap_or(dirfd) - }; - - // Read linkpath and target from guest memory - if let Some(linkpath_addr) = args.linkpath() { - let linkpath: std::path::PathBuf = linkpath_addr.read(&guest.memory())?; - - if let Some(target_addr) = args.target() { - let target: std::path::PathBuf = target_addr.read(&guest.memory())?; - - // Check if this path matches a mount point - if let Some((vfs, _translated_path)) = mount_table.resolve(&linkpath) { - // Check if this is a virtual VFS (like SQLite) - if vfs.is_virtual() { - // Call VFS symlink method directly - match vfs.symlink(&target, &linkpath).await { - Ok(()) => return Ok(Some(0)), // Success - Err(e) => { - // Map VFS errors to errno - let errno = match e { - crate::vfs::VfsError::NotFound => -libc::ENOENT as i64, - crate::vfs::VfsError::PermissionDenied => -libc::EACCES as i64, - crate::vfs::VfsError::AlreadyExists => -libc::EEXIST as i64, - _ => -libc::EIO as i64, - }; - return Ok(Some(errno)); - } - } - } - } - - if let Some(new_linkpath_addr) = - translate_path(guest, linkpath_addr, mount_table).await? - { - let new_syscall = reverie::syscalls::Symlinkat::new() - .with_target(args.target()) - .with_newdirfd(kernel_dirfd) - .with_linkpath(Some(new_linkpath_addr)); - - let result = guest.inject(Syscall::Symlinkat(new_syscall)).await?; - return Ok(Some(result)); - } - } - } - Ok(None) -} - -/// The `linkat` system call. -/// -/// This intercepts `linkat` system calls and translates paths according to the mount table -/// and virtualizes the dirfds. -/// Returns `Some(result)` if the syscall was handled and the result should be returned directly, -/// or `None` if the original syscall should be used. -pub async fn handle_linkat>( - guest: &mut T, - args: &reverie::syscalls::Linkat, - mount_table: &MountTable, - fd_table: &FdTable, -) -> Result, Error> { - let olddirfd = args.olddirfd(); - let newdirfd = args.newdirfd(); - - // AT_FDCWD is -100 - let kernel_olddirfd = if olddirfd == -100 { - olddirfd - } else { - fd_table.translate(olddirfd).unwrap_or(olddirfd) - }; - let kernel_newdirfd = if newdirfd == -100 { - newdirfd - } else { - fd_table.translate(newdirfd).unwrap_or(newdirfd) - }; - - // Read oldpath and newpath from guest memory - if let Some(oldpath_addr) = args.oldpath() { - let oldpath: std::path::PathBuf = oldpath_addr.read(&guest.memory())?; - - if let Some(newpath_addr) = args.newpath() { - let newpath: std::path::PathBuf = newpath_addr.read(&guest.memory())?; - - // Check if newpath matches a mount point with virtual VFS - if let Some((vfs, _translated_path)) = mount_table.resolve(&newpath) { - // Check if this is a virtual VFS (like SQLite) - if vfs.is_virtual() { - // Call VFS link method directly - match vfs.link(&oldpath, &newpath).await { - Ok(()) => return Ok(Some(0)), // Success - Err(e) => { - // Map VFS errors to errno - let errno = match e { - crate::vfs::VfsError::NotFound => -libc::ENOENT as i64, - crate::vfs::VfsError::PermissionDenied => -libc::EPERM as i64, - crate::vfs::VfsError::AlreadyExists => -libc::EEXIST as i64, - _ => -libc::EIO as i64, - }; - return Ok(Some(errno)); - } - } - } - } - - // Check if either path needs translation by consulting the mount table. - // We resolve both paths first (without guest memory allocation) to determine - // what needs translation, then allocate and inject if needed. - let oldpath_translated = mount_table.resolve(&oldpath); - let newpath_translated = mount_table.resolve(&newpath); - - match (oldpath_translated, newpath_translated) { - (Some((_vfs1, translated_oldpath)), Some((_vfs2, translated_newpath))) => { - // Both paths need translation - use std::ffi::CString; - - let old_cstr = CString::new(translated_oldpath.to_string_lossy().to_string()) - .map_err(|_| reverie::syscalls::Errno::EINVAL)?; - let new_cstr = CString::new(translated_newpath.to_string_lossy().to_string()) - .map_err(|_| reverie::syscalls::Errno::EINVAL)?; - - // Allocate space for both paths on guest stack - let old_bytes = old_cstr.as_bytes_with_nul(); - let new_bytes = new_cstr.as_bytes_with_nul(); - - let mut stack = guest.stack().await; - let old_addr: reverie::syscalls::AddrMut = stack.reserve(); - let new_addr: reverie::syscalls::AddrMut = stack.reserve(); - stack.commit()?; - - let old_byte_addr = old_addr.cast::(); - let new_byte_addr = new_addr.cast::(); - guest.memory().write_exact(old_byte_addr, old_bytes)?; - guest.memory().write_exact(new_byte_addr, new_bytes)?; - - let new_oldpath_ptr: reverie::syscalls::PathPtr = - unsafe { std::mem::transmute(old_byte_addr) }; - let new_newpath_ptr: reverie::syscalls::PathPtr = - unsafe { std::mem::transmute(new_byte_addr) }; - - let new_syscall = reverie::syscalls::Linkat::new() - .with_olddirfd(kernel_olddirfd) - .with_oldpath(Some(new_oldpath_ptr)) - .with_newdirfd(kernel_newdirfd) - .with_newpath(Some(new_newpath_ptr)) - .with_flags(args.flags()); - let result = guest.inject(Syscall::Linkat(new_syscall)).await?; - return Ok(Some(result)); - } - (Some(_), None) => { - // Only oldpath needs translation - if let Some(new_oldpath_addr) = - translate_path(guest, oldpath_addr, mount_table).await? - { - let new_syscall = reverie::syscalls::Linkat::new() - .with_olddirfd(kernel_olddirfd) - .with_oldpath(Some(new_oldpath_addr)) - .with_newdirfd(kernel_newdirfd) - .with_newpath(Some(newpath_addr)) - .with_flags(args.flags()); - let result = guest.inject(Syscall::Linkat(new_syscall)).await?; - return Ok(Some(result)); - } - } - (None, Some(_)) => { - // Only newpath needs translation - if let Some(new_newpath_addr) = - translate_path(guest, newpath_addr, mount_table).await? - { - let new_syscall = reverie::syscalls::Linkat::new() - .with_olddirfd(kernel_olddirfd) - .with_oldpath(Some(oldpath_addr)) - .with_newdirfd(kernel_newdirfd) - .with_newpath(Some(new_newpath_addr)) - .with_flags(args.flags()); - let result = guest.inject(Syscall::Linkat(new_syscall)).await?; - return Ok(Some(result)); - } - } - (None, None) => { - // Neither path needs translation - } - } - } - } - Ok(None) -} diff --git a/sandbox/src/syscall/xattr.rs b/sandbox/src/syscall/xattr.rs deleted file mode 100644 index 25bb8518..00000000 --- a/sandbox/src/syscall/xattr.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::{sandbox::Sandbox, syscall::translate_path, vfs::mount::MountTable}; -use reverie::{syscalls::Syscall, Error, Guest}; - -/// The `llistxattr` system call. -/// -/// This intercepts llistxattr syscalls and translates paths according to the mount table. -/// Returns Some(syscall) if the path was translated and should be injected, -/// or None if the original syscall should be used. -pub async fn handle_llistxattr>( - guest: &mut T, - args: &reverie::syscalls::Llistxattr, - mount_table: &MountTable, -) -> Result, Error> { - if let Some(path_addr) = args.path() { - if let Some(new_path_addr) = translate_path(guest, path_addr, mount_table).await? { - let new_syscall = args.with_path(Some(new_path_addr)); - - return Ok(Some(Syscall::Llistxattr(new_syscall))); - } - } - Ok(None) -} - -/// The `lgetxattr` system call. -/// -/// This intercepts lgetxattr syscalls and translates paths according to the mount table. -/// Returns Some(syscall) if the path was translated and should be injected, -/// or None if the original syscall should be used. -pub async fn handle_lgetxattr>( - guest: &mut T, - args: &reverie::syscalls::Lgetxattr, - mount_table: &MountTable, -) -> Result, Error> { - if let Some(path_addr) = args.path() { - if let Some(new_path_addr) = translate_path(guest, path_addr, mount_table).await? { - let new_syscall = args.with_path(Some(new_path_addr)); - - return Ok(Some(Syscall::Lgetxattr(new_syscall))); - } - } - Ok(None) -} diff --git a/sandbox/src/vfs/bind.rs b/sandbox/src/vfs/bind.rs deleted file mode 100644 index d93a6b3e..00000000 --- a/sandbox/src/vfs/bind.rs +++ /dev/null @@ -1,127 +0,0 @@ -use super::{Vfs, VfsError, VfsResult}; -use std::path::{Path, PathBuf}; - -/// A bind mount VFS that maps a sandbox path to a host directory -/// -/// This implements a simple bind mount - it translates paths under -/// a sandbox prefix to a host directory. After the refactoring in -/// commit b06115d, bind mounts no longer need FileOps implementations -/// because the syscall handlers directly use kernel FDs via -/// FdEntry::Passthrough. -#[derive(Debug, Clone)] -pub struct BindVfs { - /// The real filesystem path on the host - host_root: PathBuf, - /// The virtual path as seen by the sandboxed process - sandbox_root: PathBuf, -} - -impl BindVfs { - /// Create a new bind mount VFS - /// - /// # Arguments - /// * `host_root` - The real directory on the host filesystem - /// * `sandbox_root` - The virtual path seen by the guest - pub fn new(host_root: PathBuf, sandbox_root: PathBuf) -> Self { - Self { - host_root, - sandbox_root, - } - } - - /// Get the host root path - pub fn host_root(&self) -> &Path { - &self.host_root - } - - /// Get the sandbox root path - pub fn sandbox_root(&self) -> &Path { - &self.sandbox_root - } -} - -#[async_trait::async_trait] -impl Vfs for BindVfs { - fn translate_path(&self, path: &Path) -> VfsResult { - // Check if the path is under our sandbox root - let sandbox_str = self - .sandbox_root - .to_str() - .ok_or_else(|| VfsError::InvalidInput("Invalid sandbox path".to_string()))?; - - let path_str = path - .to_str() - .ok_or_else(|| VfsError::InvalidInput("Invalid path".to_string()))?; - - // Check for exact match or prefix match - if path_str == sandbox_str || path_str.starts_with(&format!("{}/", sandbox_str)) { - // Extract the relative part - let relative = path_str - .strip_prefix(sandbox_str) - .unwrap_or("") - .trim_start_matches('/'); - - // Construct the host path - let host_path = if relative.is_empty() { - self.host_root.clone() - } else { - self.host_root.join(relative) - }; - - Ok(host_path) - } else { - Err(VfsError::NotFound) - } - } - - fn is_virtual(&self) -> bool { - // Bind mounts are not virtual - they use real kernel file descriptors - false - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_translate_path_exact_match() { - let vfs = BindVfs::new(PathBuf::from("/tmp/agent"), PathBuf::from("/agent")); - - let result = vfs.translate_path(Path::new("/agent")).unwrap(); - assert_eq!(result, PathBuf::from("/tmp/agent")); - } - - #[test] - fn test_translate_path_with_subpath() { - let vfs = BindVfs::new(PathBuf::from("/tmp/agent"), PathBuf::from("/agent")); - - let result = vfs - .translate_path(Path::new("/agent/subdir/file.txt")) - .unwrap(); - assert_eq!(result, PathBuf::from("/tmp/agent/subdir/file.txt")); - } - - #[test] - fn test_translate_path_no_match() { - let vfs = BindVfs::new(PathBuf::from("/tmp/agent"), PathBuf::from("/agent")); - - let result = vfs.translate_path(Path::new("/other/path")); - assert!(result.is_err()); - } - - #[test] - fn test_translate_path_partial_match() { - let vfs = BindVfs::new(PathBuf::from("/tmp/agent"), PathBuf::from("/agent")); - - // /agentfoo should not match /agent - let result = vfs.translate_path(Path::new("/agentfoo")); - assert!(result.is_err()); - } - - #[test] - fn test_is_not_virtual() { - let vfs = BindVfs::new(PathBuf::from("/tmp/agent"), PathBuf::from("/agent")); - assert!(!vfs.is_virtual()); - } -} diff --git a/sandbox/src/vfs/fdtable.rs b/sandbox/src/vfs/fdtable.rs deleted file mode 100644 index eb483b4c..00000000 --- a/sandbox/src/vfs/fdtable.rs +++ /dev/null @@ -1,766 +0,0 @@ -use super::file::BoxedFileOps; -use std::collections::{BinaryHeap, HashMap}; -use std::sync::{Arc, Mutex}; - -/// Standard file descriptor constants -const STDIN_FILENO: i32 = 0; -const STDOUT_FILENO: i32 = 1; -const STDERR_FILENO: i32 = 2; -const FIRST_USER_FD: i32 = 3; - -/// Information about a virtualized file descriptor -#[derive(Clone)] -pub enum FdEntry { - /// Passthrough file - just maps virtual FD to kernel FD - Passthrough { - kernel_fd: i32, - flags: i32, - path: Option, - }, - /// Virtual file - has FileOps implementation - Virtual { - file_ops: BoxedFileOps, - flags: i32, - path: Option, - }, -} - -impl FdEntry { - /// Get the kernel file descriptor if this is a passthrough file - pub fn kernel_fd(&self) -> Option { - match self { - FdEntry::Passthrough { kernel_fd, .. } => Some(*kernel_fd), - FdEntry::Virtual { .. } => None, - } - } - - /// Get the flags for this FD entry - pub fn flags(&self) -> i32 { - match self { - FdEntry::Passthrough { flags, .. } => *flags, - FdEntry::Virtual { flags, .. } => *flags, - } - } - - /// Get the path for this FD entry - pub fn path(&self) -> Option<&std::path::PathBuf> { - match self { - FdEntry::Passthrough { path, .. } => path.as_ref(), - FdEntry::Virtual { path, .. } => path.as_ref(), - } - } - - /// Get the file_ops for virtual files - pub fn file_ops(&self) -> Option<&BoxedFileOps> { - match self { - FdEntry::Passthrough { .. } => None, - FdEntry::Virtual { file_ops, .. } => Some(file_ops), - } - } -} - -/// Inner state of the FD table, protected by a single mutex -struct FdTableInner { - /// Mapping from virtual FD to kernel FD - entries: HashMap, - /// Next virtual FD to allocate (monotonically increasing) - next_vfd: i32, - /// Min-heap of freed FDs available for reuse (stored as negative for min-heap behavior) - free_fds: BinaryHeap>, -} - -/// Per-process file descriptor table that virtualizes file descriptors -/// -/// This table maintains a mapping from virtual (process-visible) file descriptors -/// to kernel (actual) file descriptors. It is thread-safe and can be shared across -/// threads within the same process. -/// -/// Note: Clone creates a shallow copy that shares the same underlying FD table. -/// For fork/clone syscalls, use `deep_clone()` instead. -#[derive(Clone)] -pub struct FdTable { - inner: Arc>, -} - -impl FdTable { - /// Create a new FD table with standard FDs (stdin, stdout, stderr) - pub fn new() -> Self { - let mut entries = HashMap::new(); - - // Initialize standard file descriptors (0, 1, 2) as passthrough files - entries.insert( - STDIN_FILENO, - FdEntry::Passthrough { - kernel_fd: STDIN_FILENO, - flags: 0, - path: None, - }, - ); - entries.insert( - STDOUT_FILENO, - FdEntry::Passthrough { - kernel_fd: STDOUT_FILENO, - flags: 0, - path: None, - }, - ); - entries.insert( - STDERR_FILENO, - FdEntry::Passthrough { - kernel_fd: STDERR_FILENO, - flags: 0, - path: None, - }, - ); - - Self { - inner: Arc::new(Mutex::new(FdTableInner { - entries, - next_vfd: FIRST_USER_FD, - free_fds: BinaryHeap::new(), - })), - } - } - - /// Create a deep clone of this FD table (for fork/clone syscalls) - /// - /// This creates a completely independent copy of the FD table, - /// unlike the default Clone which shares the underlying table. - pub fn deep_clone(&self) -> Self { - let inner = self - .inner - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - - Self { - inner: Arc::new(Mutex::new(FdTableInner { - entries: inner.entries.clone(), - next_vfd: inner.next_vfd, - free_fds: inner.free_fds.clone(), - })), - } - } - - /// Allocate a new virtual FD for the given FdEntry - /// - /// This uses the lowest available FD number, as required by POSIX. - pub fn allocate(&self, entry: FdEntry) -> i32 { - let mut inner = self - .inner - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - - // Try to reuse a freed FD first (POSIX requires lowest available FD) - let vfd = if let Some(std::cmp::Reverse(free_fd)) = inner.free_fds.pop() { - free_fd - } else { - // No free FDs, allocate a new one - let vfd = inner.next_vfd; - if vfd == i32::MAX { - // FD exhaustion - search for gaps in allocated FDs - // This is a rare edge case - (FIRST_USER_FD..i32::MAX) - .find(|fd| !inner.entries.contains_key(fd)) - .expect("File descriptor table exhausted") - } else { - inner.next_vfd += 1; - vfd - } - }; - - inner.entries.insert(vfd, entry); - vfd - } - - /// Allocate a new virtual FD at or above the specified minimum - /// - /// This is used for fcntl F_DUPFD and F_DUPFD_CLOEXEC commands. - pub fn allocate_min(&self, min_vfd: i32, entry: FdEntry) -> i32 { - let mut inner = self - .inner - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - - // Find the lowest available FD >= min_vfd - let vfd = (min_vfd..i32::MAX) - .find(|fd| !inner.entries.contains_key(fd)) - .expect("File descriptor table exhausted"); - - // Update next_vfd if we allocated beyond it - if vfd >= inner.next_vfd { - // We just "skipped over" the range [next_vfd, vfd). Those FDs are now - // valid, unused, and must be eligible for `allocate()` (lowest-available). - let mut fd = inner.next_vfd; - while fd < vfd { - if fd >= FIRST_USER_FD && !inner.entries.contains_key(&fd) { - inner.free_fds.push(std::cmp::Reverse(fd)); - } - fd = fd.checked_add(1).expect("fd overflow"); - } - inner.next_vfd = vfd + 1; - } - - // Remove from free list if it was there - inner.free_fds = inner - .free_fds - .clone() - .into_iter() - .filter(|&std::cmp::Reverse(fd)| fd != vfd) - .collect(); - - inner.entries.insert(vfd, entry); - vfd - } - - /// Allocate a specific virtual FD (used for dup2) - /// - /// Returns the old FdEntry if the VFD was already allocated, which the caller - /// should close if needed. - pub fn allocate_at(&self, vfd: i32, entry: FdEntry) -> Option { - let mut inner = self - .inner - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - - // Remove the FD from free list if it's there - // (This is inefficient but dup2 to freed FDs is rare) - inner.free_fds = inner - .free_fds - .clone() - .into_iter() - .filter(|&std::cmp::Reverse(fd)| fd != vfd) - .collect(); - - // Update next_vfd if necessary - if vfd >= inner.next_vfd { - // Like `allocate_min`, allocating at a far FD creates gaps that must remain - // available for subsequent `allocate()` calls. - let mut fd = inner.next_vfd; - while fd < vfd { - if fd >= FIRST_USER_FD && !inner.entries.contains_key(&fd) { - inner.free_fds.push(std::cmp::Reverse(fd)); - } - fd = fd.checked_add(1).expect("fd overflow"); - } - inner.next_vfd = vfd + 1; - } - - // Insert the new entry and return the old one if it existed - inner.entries.insert(vfd, entry) - } - - /// Translate a virtual FD to a kernel FD - /// - /// Returns the kernel FD if this is a passthrough file, or None if it's a - /// virtualized file or the VFD doesn't exist. - pub fn translate(&self, vfd: i32) -> Option { - let inner = self - .inner - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - inner.entries.get(&vfd).and_then(|entry| entry.kernel_fd()) - } - - /// Get the full entry for a virtual FD - pub fn get(&self, vfd: i32) -> Option { - let inner = self - .inner - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - inner.entries.get(&vfd).cloned() - } - - /// Deallocate a virtual FD and mark it as available for reuse - pub fn deallocate(&self, vfd: i32) -> Option { - let mut inner = self - .inner - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - - let entry = inner.entries.remove(&vfd)?; - - // Add to free list for reuse (unless it's a standard FD) - if vfd >= FIRST_USER_FD { - inner.free_fds.push(std::cmp::Reverse(vfd)); - } - - Some(entry) - } - - /// Duplicate a virtual FD (for dup syscall) - pub fn duplicate(&self, old_vfd: i32) -> Option { - let entry = self.get(old_vfd)?; - // Allocate a new virtual FD pointing to the same file operations - Some(self.allocate(entry)) - } - - /// Duplicate a virtual FD to a specific new FD (for dup2 syscall) - /// - /// Returns the old entry that was at new_vfd if it existed (caller should close it) - pub fn duplicate_at(&self, old_vfd: i32, new_vfd: i32) -> Option { - let entry = self.get(old_vfd)?; - self.allocate_at(new_vfd, entry) - } -} - -impl Default for FdTable { - fn default() -> Self { - Self::new() - } -} - -impl std::fmt::Debug for FdTable { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let inner = self.inner.lock().unwrap(); - f.debug_struct("FdTable") - .field("entry_count", &inner.entries.len()) - .field("next_vfd", &inner.next_vfd) - .field("free_fds_count", &inner.free_fds.len()) - .finish() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_standard_fds() { - let table = FdTable::new(); - - assert_eq!(table.translate(0), Some(0)); // stdin - assert_eq!(table.translate(1), Some(1)); // stdout - assert_eq!(table.translate(2), Some(2)); // stderr - } - - #[test] - fn test_allocate() { - let table = FdTable::new(); - - let entry1 = FdEntry::Passthrough { - kernel_fd: 100, - flags: 0, - path: None, - }; - let vfd1 = table.allocate(entry1); - assert_eq!(vfd1, 3); // First non-standard FD - assert_eq!(table.translate(3), Some(100)); - - let entry2 = FdEntry::Passthrough { - kernel_fd: 101, - flags: 0, - path: None, - }; - let vfd2 = table.allocate(entry2); - assert_eq!(vfd2, 4); - assert_eq!(table.translate(4), Some(101)); - } - - #[test] - fn test_deallocate() { - let table = FdTable::new(); - - let entry = FdEntry::Passthrough { - kernel_fd: 100, - flags: 0, - path: None, - }; - let vfd = table.allocate(entry); - assert_eq!(table.translate(vfd), Some(100)); - - let entry = table.deallocate(vfd); - assert!(entry.is_some()); - assert_eq!(entry.unwrap().kernel_fd(), Some(100)); - - assert_eq!(table.translate(vfd), None); - } - - #[test] - fn test_duplicate() { - let table = FdTable::new(); - - let entry = FdEntry::Passthrough { - kernel_fd: 100, - flags: 0, - path: None, - }; - let vfd1 = table.allocate(entry); - let vfd2 = table.duplicate(vfd1).unwrap(); - - assert_ne!(vfd1, vfd2); - assert_eq!(table.translate(vfd1), Some(100)); - assert_eq!(table.translate(vfd2), Some(100)); - } - - #[test] - fn test_duplicate_at() { - let table = FdTable::new(); - - let entry = FdEntry::Passthrough { - kernel_fd: 100, - flags: 0, - path: None, - }; - let vfd1 = table.allocate(entry); - let result = table.duplicate_at(vfd1, 10); - - // duplicate_at returns the old FdEntry that was at new_vfd (if any) - // In this case, there was no previous entry at fd 10, so it returns None - assert!(result.is_none()); - assert_eq!(table.translate(10), Some(100)); - } -} - -/// Property tests for `FdTable` correctness. -/// -/// These focus on POSIX-like allocation semantics: -/// - `allocate()` must return the lowest available FD (>= 3) -/// - `allocate_min()` must return the lowest available FD >= `min` -/// - `allocate_at()` overwrites the target FD and returns the previous entry (if any) -#[cfg(test)] -mod prop_tests { - use super::*; - use proptest::prelude::*; - use std::collections::BTreeMap; - - fn mk_entry(kernel_fd: i32) -> FdEntry { - FdEntry::Passthrough { - kernel_fd, - flags: 0, - path: None, - } - } - - fn lowest_available(allocated: &BTreeMap) -> i32 { - let mut fd = FIRST_USER_FD; - loop { - if !allocated.contains_key(&fd) { - return fd; - } - fd = fd.checked_add(1).expect("fd overflow in test model"); - } - } - - fn lowest_available_min(allocated: &BTreeMap, min_fd: i32) -> i32 { - let mut fd = std::cmp::max(FIRST_USER_FD, min_fd); - loop { - if !allocated.contains_key(&fd) { - return fd; - } - fd = fd.checked_add(1).expect("fd overflow in test model"); - } - } - - #[derive(Debug, Clone)] - enum Op { - /// Fork-like event: create an independent copy via `deep_clone()`. - DeepClone, - Allocate { - kernel_fd: i32, - }, - AllocateMin { - min_fd: i32, - kernel_fd: i32, - }, - AllocateAt { - vfd: i32, - kernel_fd: i32, - }, - Deallocate { - vfd: i32, - }, - Duplicate { - old_vfd: i32, - }, - DuplicateAt { - old_vfd: i32, - new_vfd: i32, - }, - } - - fn ops_strategy() -> impl Strategy> { - // Keep FD numbers small so we can cheaply assert table/model equivalence - // over a bounded range, while still allowing "gaps" and overwrites. - let vfd = FIRST_USER_FD..80i32; - let min_fd = FIRST_USER_FD..80i32; - let kernel_fd = 3i32..10_000i32; - - // Randomize the skew (weight profile) per generated test case so we cover - // multiple "workloads" instead of baking in one distribution. - // - // We keep weights bounded so generation is efficient and reproducible. - // (proptest seeds determine the weights deterministically per case) - ( - 1u32..=10, // deep_clone - 10u32..=70, // allocate - 10u32..=70, // deallocate - 5u32..=50, // duplicate - 1u32..=30, // allocate_min - 1u32..=20, // allocate_at - 1u32..=20, // duplicate_at - ) - .prop_flat_map( - move |( - w_deep_clone, - w_alloc, - w_dealloc, - w_dup, - w_alloc_min, - w_alloc_at, - w_dup_at, - )| { - let total = w_alloc - .saturating_add(w_dealloc) - .saturating_add(w_dup) - .saturating_add(w_alloc_min) - .saturating_add(w_alloc_at) - .saturating_add(w_dup_at); - let total = total.saturating_add(w_deep_clone); - - // total is always > 0 because allocate/deallocate/duplicate are >= 1. - prop::collection::vec( - ( - 0u32..total, - vfd.clone(), - min_fd.clone(), - kernel_fd.clone(), - vfd.clone(), - ), - 0..600, - ) - .prop_map(move |items| { - items - .into_iter() - .map(|(pick, fd1, minfd, kfd, fd2)| { - let mut acc = 0u32; - - acc = acc.saturating_add(w_deep_clone); - if pick < acc { - return Op::DeepClone; - } - - acc = acc.saturating_add(w_alloc); - if pick < acc { - return Op::Allocate { kernel_fd: kfd }; - } - - acc = acc.saturating_add(w_dealloc); - if pick < acc { - return Op::Deallocate { vfd: fd1 }; - } - - acc = acc.saturating_add(w_dup); - if pick < acc { - return Op::Duplicate { old_vfd: fd1 }; - } - - acc = acc.saturating_add(w_alloc_min); - if pick < acc { - return Op::AllocateMin { - min_fd: minfd, - kernel_fd: kfd, - }; - } - - acc = acc.saturating_add(w_alloc_at); - if pick < acc { - return Op::AllocateAt { - vfd: fd1, - kernel_fd: kfd, - }; - } - - // Remaining range maps to duplicate_at - Op::DuplicateAt { - old_vfd: fd1, - new_vfd: fd2, - } - }) - .collect() - }) - }, - ) - } - - fn assert_model_matches(table: &FdTable, model: &BTreeMap) { - // Check all model FDs translate to expected kernel FDs. - for (&vfd, &kfd) in model { - assert_eq!( - table.translate(vfd), - Some(kfd), - "translate({}) mismatch", - vfd - ); - } - - // Spot-check a bounded range to ensure we don't have "ghost" entries. - for vfd in FIRST_USER_FD..120 { - if let Some(&kfd) = model.get(&vfd) { - assert_eq!(table.translate(vfd), Some(kfd)); - } else { - assert_eq!(table.translate(vfd), None); - } - } - } - - proptest! { - #![proptest_config(ProptestConfig { - cases: 256, - max_shrink_iters: 20_000, - .. ProptestConfig::default() - })] - - #[test] - fn prop_fdtable_allocation_semantics(ops in ops_strategy()) { - // Keep multiple independent tables around to model fork/clone behavior. - // (DeepClone creates a new independent FD table snapshot.) - let mut tables: Vec = vec![FdTable::new()]; - let mut models: Vec> = vec![BTreeMap::new()]; - - for op in ops { - // Pick an active table index in a stable (seed-driven) way. - // We re-use the tagged integers produced by the strategy to spread ops. - let table_idx = match &op { - Op::DeepClone => 0usize, - Op::Allocate { kernel_fd } => (kernel_fd.unsigned_abs() as usize) % tables.len(), - Op::AllocateMin { min_fd, .. } => (min_fd.unsigned_abs() as usize) % tables.len(), - Op::AllocateAt { vfd, .. } => (vfd.unsigned_abs() as usize) % tables.len(), - Op::Deallocate { vfd } => (vfd.unsigned_abs() as usize) % tables.len(), - Op::Duplicate { old_vfd } => (old_vfd.unsigned_abs() as usize) % tables.len(), - Op::DuplicateAt { old_vfd, .. } => (old_vfd.unsigned_abs() as usize) % tables.len(), - }; - - // Helper to bias selection toward existing FDs (more realistic). - // Uses an input "seed" (an int embedded in the op) to pick a stable key. - let choose_existing = |model: &BTreeMap, seed: i32, fallback: i32| -> i32 { - if model.is_empty() { - return fallback; - } - // Sometimes intentionally *don't* pick an existing fd, so we also exercise - // error paths (e.g., dup/dealloc on non-existent fd). - if (seed.unsigned_abs() % 3) != 0 { - return fallback; - } - let idx = (seed.unsigned_abs() as usize) % model.len(); - *model.keys().nth(idx).unwrap() - }; - - match op { - Op::DeepClone => { - // Fork-like: clone the currently selected table/model. - // Keep fan-out bounded so tests don't get too slow. - if tables.len() < 4 { - // Clone from the selected table index. - let t = tables[table_idx].deep_clone(); - let m = models[table_idx].clone(); - tables.push(t); - models.push(m); - } - } - Op::Allocate { kernel_fd } => { - let table = &tables[table_idx]; - let model = &mut models[table_idx]; - let expected_vfd = lowest_available(model); - let got = table.allocate(mk_entry(kernel_fd)); - prop_assert_eq!(got, expected_vfd); - model.insert(got, kernel_fd); - } - Op::AllocateMin { min_fd, kernel_fd } => { - let table = &tables[table_idx]; - let model = &mut models[table_idx]; - // Make allocate_min interact with existing state by sometimes using - // min_fd relative to an existing FD (classic dupfd patterns). - let derived_min = if !model.is_empty() && (min_fd % 2 == 0) { - let base = choose_existing(model, min_fd, FIRST_USER_FD); - std::cmp::max(FIRST_USER_FD, base.saturating_add((min_fd % 11).abs())) - } else { - min_fd - }; - - let expected_vfd = lowest_available_min(model, derived_min); - let got = table.allocate_min(derived_min, mk_entry(kernel_fd)); - prop_assert_eq!(got, expected_vfd); - model.insert(got, kernel_fd); - } - Op::AllocateAt { vfd, kernel_fd } => { - let table = &tables[table_idx]; - let model = &mut models[table_idx]; - let target = choose_existing(model, vfd, vfd); - let expected_old = model.get(&target).copied(); - let old = table.allocate_at(target, mk_entry(kernel_fd)); - match (expected_old, old) { - (None, None) => {} - (Some(k), Some(e)) => prop_assert_eq!(e.kernel_fd(), Some(k)), - (a, b) => prop_assert!(false, "allocate_at({}) old mismatch: expected {:?}, got {:?}", target, a, b.as_ref().and_then(|e| e.kernel_fd())), - } - model.insert(target, kernel_fd); - } - Op::Deallocate { vfd } => { - let table = &tables[table_idx]; - let model = &mut models[table_idx]; - let target = choose_existing(model, vfd, vfd); - let expected = model.remove(&target); - let got = table.deallocate(target).and_then(|e| e.kernel_fd()); - prop_assert_eq!(got, expected); - } - Op::Duplicate { old_vfd } => { - let table = &tables[table_idx]; - let model = &mut models[table_idx]; - let src = choose_existing(model, old_vfd, old_vfd); - let expected_kernel = model.get(&src).copied(); - let got = table.duplicate(src); - - match (expected_kernel, got) { - (None, None) => {} - (Some(kfd), Some(new_vfd)) => { - // dup allocates a new FD at the lowest available number. - let expected_vfd = lowest_available(model); - prop_assert_eq!(new_vfd, expected_vfd); - model.insert(new_vfd, kfd); - } - (a, b) => prop_assert!(false, "duplicate({}) mismatch: expected {:?}, got {:?}", src, a.map(|_| "Some"), b.map(|_| "Some")), - } - } - Op::DuplicateAt { old_vfd, new_vfd } => { - let table = &tables[table_idx]; - let model = &mut models[table_idx]; - let src = choose_existing(model, old_vfd, old_vfd); - // For the destination, also bias toward existing FDs sometimes - // to force overwrite paths, but allow new FDs as well. - let dst = if !model.is_empty() && (new_vfd % 2 == 0) { - choose_existing(model, new_vfd, new_vfd) - } else { - new_vfd - }; - - let expected_kernel = model.get(&src).copied(); - let expected_old_at_dest = model.get(&dst).copied(); - - let old = table.duplicate_at(src, dst); - - match expected_kernel { - None => { - prop_assert!(old.is_none(), "duplicate_at should fail when old_vfd doesn't exist"); - // Model unchanged - } - Some(kfd) => { - // If dest had something, duplicate_at returns it. - match (expected_old_at_dest, old) { - (None, None) => {} - (Some(k), Some(e)) => prop_assert_eq!(e.kernel_fd(), Some(k)), - (a, b) => prop_assert!(false, "duplicate_at old-at-dest mismatch: expected {:?}, got {:?}", a, b.as_ref().and_then(|e| e.kernel_fd())), - } - model.insert(dst, kfd); - } - } - } - } - - // After each step, the observable translation must match the model for ALL tables. - for (t, m) in tables.iter().zip(models.iter()) { - assert_model_matches(t, m); - } - } - } - } -} diff --git a/sandbox/src/vfs/file.rs b/sandbox/src/vfs/file.rs deleted file mode 100644 index da3d8512..00000000 --- a/sandbox/src/vfs/file.rs +++ /dev/null @@ -1,62 +0,0 @@ -use super::VfsResult; -use async_trait::async_trait; -use std::os::unix::io::RawFd; -use std::sync::Arc; - -/// File operations trait for VFS implementations. -/// -/// This trait provides a VFS-level abstraction over file operations, -/// allowing different implementations (passthrough, bind mount, SQLite VFS, etc.) -/// to handle file I/O differently. -#[async_trait] -pub trait FileOps: Send + Sync { - /// Read from the file at the current offset - async fn read(&self, buf: &mut [u8]) -> VfsResult; - - /// Write to the file at the current offset - async fn write(&self, buf: &[u8]) -> VfsResult; - - /// Seek to a position in the file - async fn seek(&self, offset: i64, whence: i32) -> VfsResult; - - /// Get file status - async fn fstat(&self) -> VfsResult; - - /// Sync file data to storage - async fn fsync(&self) -> VfsResult<()>; - - /// Sync file data (but not metadata) to storage - async fn fdatasync(&self) -> VfsResult<()>; - - /// Perform file control operations - fn fcntl(&self, cmd: i32, arg: i64) -> VfsResult; - - /// Perform device-specific I/O operations - fn ioctl(&self, request: u64, arg: u64) -> VfsResult; - - /// Get the underlying kernel file descriptor (if any) - /// - /// Returns None for virtualized files that don't have a real kernel FD. - /// Some operations may need to fall back to the kernel FD. - fn as_raw_fd(&self) -> Option; - - /// Close the file - async fn close(&self) -> VfsResult<()>; - - /// Get flags associated with this file descriptor - fn get_flags(&self) -> i32; - - /// Set flags associated with this file descriptor - fn set_flags(&self, flags: i32) -> VfsResult<()>; - - /// Read directory entries (for directories only) - /// - /// This is used to implement getdents64. Returns a vector of (inode, name, type) tuples. - /// Returns an error if this is not a directory. - async fn getdents(&self) -> VfsResult> { - Err(super::VfsError::Other("Not a directory".to_string())) - } -} - -/// A boxed FileOps trait object for dynamic dispatch -pub type BoxedFileOps = Arc; diff --git a/sandbox/src/vfs/mod.rs b/sandbox/src/vfs/mod.rs deleted file mode 100644 index f54ffbb5..00000000 --- a/sandbox/src/vfs/mod.rs +++ /dev/null @@ -1,127 +0,0 @@ -pub mod bind; -pub mod fdtable; -pub mod file; -pub mod mount; -#[cfg(target_os = "linux")] -pub mod sqlite; - -use async_trait::async_trait; -use std::path::{Path, PathBuf}; -use std::result::Result as StdResult; - -/// VFS error type -#[derive(Debug)] -pub enum VfsError { - NotFound, - PermissionDenied, - AlreadyExists, - InvalidInput(String), - IoError(std::io::Error), - Other(String), -} - -impl From for VfsError { - fn from(err: std::io::Error) -> Self { - VfsError::IoError(err) - } -} - -impl std::fmt::Display for VfsError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - VfsError::NotFound => write!(f, "Not found"), - VfsError::PermissionDenied => write!(f, "Permission denied"), - VfsError::AlreadyExists => write!(f, "Already exists"), - VfsError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg), - VfsError::IoError(err) => write!(f, "IO error: {}", err), - VfsError::Other(msg) => write!(f, "{}", msg), - } - } -} - -impl std::error::Error for VfsError {} - -pub type VfsResult = StdResult; - -use file::BoxedFileOps; - -/// Virtual file system trait. -/// -/// This trait provides a Linux VFS-like interface for implementing -/// different filesystem backends. -#[async_trait] -pub trait Vfs: Send + Sync { - /// Translate a sandbox path to the actual backend path - /// - /// This is the core operation for path-based VFS implementations. - /// It maps a guest/sandbox path to the real path that should be used. - fn translate_path(&self, path: &Path) -> VfsResult; - - /// Check if this VFS is purely virtual (no kernel file descriptors) - /// - /// Returns true if files are stored entirely in the VFS (like SQLite), - /// false if they use kernel file descriptors (like passthrough). - fn is_virtual(&self) -> bool { - false - } - - /// Open a file directly in the VFS (for virtual filesystems) - /// - /// This is only called for virtual VFS implementations. For passthrough - async fn open(&self, _path: &Path, _flags: i32, _mode: u32) -> VfsResult { - Err(VfsError::Other( - "open() not supported by this VFS".to_string(), - )) - } - - /// Get file status directly from the VFS (for virtual filesystems) - /// This follows symlinks. - /// - /// This is only called for virtual VFS implementations. For passthrough - /// VFS, the kernel handles stat operations. - async fn stat(&self, _path: &Path) -> VfsResult { - Err(VfsError::Other( - "stat() not supported by this VFS".to_string(), - )) - } - - /// Get file status without following symlinks (for virtual filesystems) - /// - /// This is only called for virtual VFS implementations. - async fn lstat(&self, _path: &Path) -> VfsResult { - Err(VfsError::Other( - "lstat() not supported by this VFS".to_string(), - )) - } - - /// Create a symbolic link (for virtual filesystems) - /// - /// This is only called for virtual VFS implementations. - async fn symlink(&self, _target: &Path, _linkpath: &Path) -> VfsResult<()> { - Err(VfsError::Other( - "symlink() not supported by this VFS".to_string(), - )) - } - - /// Read the target of a symbolic link (for virtual filesystems) - /// - /// This is only called for virtual VFS implementations. - async fn readlink(&self, _path: &Path) -> VfsResult { - Err(VfsError::Other( - "readlink() not supported by this VFS".to_string(), - )) - } - - /// Create a hard link (for virtual filesystems) - /// - /// Creates a new directory entry `newpath` that refers to the same inode as `oldpath`. - /// This is only called for virtual VFS implementations. - async fn link(&self, _oldpath: &Path, _newpath: &Path) -> VfsResult<()> { - Err(VfsError::Other( - "link() not supported by this VFS".to_string(), - )) - } -} - -/// A boxed VFS trait object for dynamic dispatch -pub type BoxedVfs = Box; diff --git a/sandbox/src/vfs/mount.rs b/sandbox/src/vfs/mount.rs deleted file mode 100644 index c5907ff2..00000000 --- a/sandbox/src/vfs/mount.rs +++ /dev/null @@ -1,368 +0,0 @@ -use super::Vfs; -use serde::{Deserialize, Serialize}; -use std::{ - cmp::Reverse, - path::{Path, PathBuf}, - sync::Arc, -}; - -/// A mount point entry in the mount table -#[derive(Clone)] -pub struct MountPoint { - /// The virtual path as seen by the sandboxed process - pub sandbox_path: PathBuf, - /// The VFS implementation for this mount point - pub vfs: Arc, -} - -/// Mount table manages multiple VFS mount points -/// -/// This is similar to Linux's VFS mount table - it tracks multiple -/// mounted filesystems and resolves paths to the appropriate VFS -/// implementation using longest-prefix matching. -#[derive(Clone)] -pub struct MountTable { - mounts: Vec, -} - -impl MountTable { - /// Create a new empty mount table - pub fn new() -> Self { - Self { mounts: Vec::new() } - } - - /// Add a new mount point - /// - /// Mount points are automatically sorted by path depth (longest first) - /// to ensure longest-prefix matching works correctly. - pub fn add_mount(&mut self, sandbox_path: PathBuf, vfs: Arc) { - self.mounts.push(MountPoint { sandbox_path, vfs }); - // Sort by path depth (deepest first) to implement longest-prefix matching - self.mounts - .sort_by_key(|m| Reverse(m.sandbox_path.components().count())); - } - - /// Resolve a path to a VFS and translated path - /// - /// This implements longest-prefix matching - if multiple mount points - /// could match, the one with the longest matching prefix is chosen. - /// - /// Returns None if no mount point matches the path. - pub fn resolve(&self, path: &Path) -> Option<(Arc, PathBuf)> { - for mount in &self.mounts { - // Try to translate the path using this mount's VFS - if let Ok(translated) = mount.vfs.translate_path(path) { - return Some((mount.vfs.clone(), translated)); - } - } - None - } - - /// Get all mount points - pub fn mounts(&self) -> &[MountPoint] { - &self.mounts - } -} - -impl Default for MountTable { - fn default() -> Self { - Self::new() - } -} - -impl std::fmt::Debug for MountTable { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MountTable") - .field("mount_count", &self.mounts.len()) - .finish() - } -} - -/// Type of VFS mount supported by the sandbox. -/// -/// This enum defines the different ways to make host resources available -/// to sandboxed processes, similar to mount types in traditional Unix systems. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum MountType { - /// Bind mount that passes through to a host path. - /// - /// Similar to Linux bind mounts, this makes a host directory tree - /// available at a path within the sandbox. The source path is - /// canonicalized (symlinks are resolved) during parsing. - Bind { - /// Source path on the host (canonicalized). - src: PathBuf, - }, - /// SQLite-backed virtual filesystem. - /// - /// This mount type creates a full POSIX-like filesystem stored in a - /// SQLite database, providing persistence and atomic operations. - Sqlite { - /// Path to the SQLite database file. - src: PathBuf, - }, -} - -/// Configuration for a mount point (used for CLI parsing). -/// -/// Mount specifications follow Docker-style syntax with key=value pairs: -/// `type=bind,src=/host/path,dst=/sandbox/path` -/// -/// Aliases are supported: `source` for `src`, `target` for `dst`. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MountConfig { - /// Type of mount. - pub mount_type: MountType, - /// Destination path in the sandbox (must be absolute). - pub dst: PathBuf, -} - -impl std::str::FromStr for MountConfig { - type Err = String; - - fn from_str(s: &str) -> Result { - use std::collections::HashMap; - - // Parse key=value pairs separated by commas - let mut options: HashMap = HashMap::new(); - - for part in s.split(',') { - let kv: Vec<&str> = part.splitn(2, '=').collect(); - if kv.len() != 2 { - return Err(format!( - "Invalid mount option '{}'. Expected format: key=value.", - part - )); - } - // Check for duplicate keys - if options - .insert(kv[0].to_string(), kv[1].to_string()) - .is_some() - { - return Err(format!("Duplicate key '{}' in mount specification.", kv[0])); - } - } - - // Check for required 'type' field - let mount_type = options.get("type").ok_or_else(|| { - "Missing required field 'type'. Example: type=bind,src=/host/path,dst=/sandbox/path." - .to_string() - })?; - - match mount_type.as_str() { - "bind" => { - // Get src (or source as alias) - let src_str = options.get("src") - .or_else(|| options.get("source")) - .ok_or_else(|| { - "Bind mount requires 'src' field. Example: type=bind,src=/host/path,dst=/sandbox/path.".to_string() - })?; - - // Get dst (or target as alias) - let dst_str = options.get("dst") - .or_else(|| options.get("target")) - .ok_or_else(|| { - "Bind mount requires 'dst' field. Example: type=bind,src=/host/path,dst=/sandbox/path.".to_string() - })?; - - // Validate destination is absolute - let dst = PathBuf::from(dst_str); - if !dst.is_absolute() { - return Err(format!("Destination path '{}' must be absolute.", dst_str)); - } - - // Canonicalize the source path - let src = std::fs::canonicalize(src_str).map_err(|e| { - format!("Failed to canonicalize source path '{}': {}.", src_str, e) - })?; - - Ok(MountConfig { - mount_type: MountType::Bind { src }, - dst, - }) - } - "sqlite" => { - // Get src (or source as alias) - let src_str = options.get("src") - .or_else(|| options.get("source")) - .ok_or_else(|| { - "SQLite mount requires 'src' field. Example: type=sqlite,src=agent.db,dst=/agent.".to_string() - })?; - - // Get dst (or target as alias) - let dst_str = options.get("dst") - .or_else(|| options.get("target")) - .ok_or_else(|| { - "SQLite mount requires 'dst' field. Example: type=sqlite,src=agent.db,dst=/agent.".to_string() - })?; - - // Validate destination is absolute - let dst = PathBuf::from(dst_str); - if !dst.is_absolute() { - return Err(format!("Destination path '{}' must be absolute.", dst_str)); - } - - // For SQLite, we use the path as-is (may be relative or absolute) - let src = PathBuf::from(src_str); - - Ok(MountConfig { - mount_type: MountType::Sqlite { src }, - dst, - }) - } - _ => Err(format!( - "Unsupported mount type '{}'. Supported types: bind, sqlite.", - mount_type - )), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::vfs::bind::BindVfs; - - #[test] - fn test_mount_table_longest_prefix() { - let mut table = MountTable::new(); - - // Add two overlapping mount points - table.add_mount( - PathBuf::from("/agent"), - Arc::new(BindVfs::new( - PathBuf::from("/tmp/agent"), - PathBuf::from("/agent"), - )), - ); - - table.add_mount( - PathBuf::from("/agent/special"), - Arc::new(BindVfs::new( - PathBuf::from("/tmp/special"), - PathBuf::from("/agent/special"), - )), - ); - - // Path /agent/special/file should match the more specific mount - let result = table.resolve(Path::new("/agent/special/file")); - assert!(result.is_some()); - - let (_, translated) = result.unwrap(); - assert_eq!(translated, PathBuf::from("/tmp/special/file")); - - // Path /agent/normal should match the less specific mount - let result = table.resolve(Path::new("/agent/normal")); - assert!(result.is_some()); - - let (_, translated) = result.unwrap(); - assert_eq!(translated, PathBuf::from("/tmp/agent/normal")); - } - - #[test] - fn test_mount_table_no_match() { - let mut table = MountTable::new(); - - table.add_mount( - PathBuf::from("/agent"), - Arc::new(BindVfs::new( - PathBuf::from("/tmp/agent"), - PathBuf::from("/agent"), - )), - ); - - let result = table.resolve(Path::new("/other/path")); - assert!(result.is_none()); - } - - #[test] - fn test_parse_bind_mount() { - // Use /tmp which should exist on all systems - let config: Result = "type=bind,src=/tmp,dst=/data".parse(); - assert!(config.is_ok()); - - let config = config.unwrap(); - match config.mount_type { - MountType::Bind { src } => { - assert_eq!(src, std::fs::canonicalize("/tmp").unwrap()); - assert_eq!(config.dst, PathBuf::from("/data")); - } - MountType::Sqlite { .. } => panic!("Expected Bind mount, got Sqlite"), - } - } - - #[test] - fn test_parse_bind_mount_with_aliases() { - // Test using 'source' and 'target' aliases - let config: Result = "type=bind,source=/tmp,target=/data".parse(); - assert!(config.is_ok()); - - let config = config.unwrap(); - match config.mount_type { - MountType::Bind { src } => { - assert_eq!(src, std::fs::canonicalize("/tmp").unwrap()); - assert_eq!(config.dst, PathBuf::from("/data")); - } - MountType::Sqlite { .. } => panic!("Expected Bind mount, got Sqlite"), - } - } - - #[test] - fn test_missing_type() { - let config: Result = "src=/tmp,dst=/data".parse(); - assert!(config.is_err()); - assert!(config - .unwrap_err() - .contains("Missing required field 'type'")); - } - - #[test] - fn test_missing_dst() { - let config: Result = "type=bind,src=/tmp".parse(); - assert!(config.is_err()); - assert!(config.unwrap_err().contains("requires 'dst' field")); - } - - #[test] - fn test_bind_missing_src() { - let config: Result = "type=bind,dst=/data".parse(); - assert!(config.is_err()); - assert!(config.unwrap_err().contains("requires 'src' field")); - } - - #[test] - fn test_invalid_type() { - let config: Result = "type=foobar,dst=/data".parse(); - assert!(config.is_err()); - assert!(config.unwrap_err().contains("Unsupported mount type")); - } - - #[test] - fn test_invalid_key_value_format() { - let config: Result = "type=bind,invalid,dst=/data".parse(); - assert!(config.is_err()); - assert!(config.unwrap_err().contains("Invalid mount option")); - } - - #[test] - fn test_duplicate_keys() { - let config: Result = "type=bind,src=/tmp,src=/var,dst=/data".parse(); - assert!(config.is_err()); - assert!(config.unwrap_err().contains("Duplicate key 'src'")); - } - - #[test] - fn test_relative_destination() { - let config: Result = "type=bind,src=/tmp,dst=relative/path".parse(); - assert!(config.is_err()); - assert!(config.unwrap_err().contains("must be absolute")); - } - - #[test] - fn test_nonexistent_source() { - let config: Result = - "type=bind,src=/nonexistent-path-12345,dst=/data".parse(); - assert!(config.is_err()); - assert!(config.unwrap_err().contains("Failed to canonicalize")); - } -} diff --git a/sandbox/src/vfs/sqlite.rs b/sandbox/src/vfs/sqlite.rs deleted file mode 100644 index ccb17745..00000000 --- a/sandbox/src/vfs/sqlite.rs +++ /dev/null @@ -1,774 +0,0 @@ -use super::file::{BoxedFileOps, FileOps}; -use super::{Vfs, VfsError, VfsResult}; -use agentfs_sdk::{filesystem::AgentFS, FileSystem}; -use std::os::unix::io::RawFd; -use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; - -/// Root inode number -const ROOT_INO: i64 = 1; - -/// A SQLite-backed virtual filesystem using the AgentFS SDK -/// -/// This implements a full POSIX-like filesystem stored in a SQLite database, -/// using the agentfs-sdk Filesystem module. -#[derive(Clone)] -pub struct SqliteVfs { - /// The filesystem from the SDK - fs: Arc, - /// The virtual path as seen by the sandboxed process - mount_point: PathBuf, -} - -impl SqliteVfs { - /// Create a new SQLite VFS - /// - /// # Arguments - /// * `db_path` - Path to the SQLite database file - /// * `mount_point` - The virtual path seen by the guest (e.g., "/agent") - pub async fn new(db_path: impl AsRef, mount_point: PathBuf) -> VfsResult { - let db_path_str = db_path - .as_ref() - .to_str() - .ok_or_else(|| VfsError::InvalidInput("Invalid database path".to_string()))?; - - let fs = AgentFS::new(db_path_str) - .await - .map_err(|e| VfsError::Other(format!("Failed to create filesystem: {}", e)))?; - - Ok(Self { - fs: Arc::new(fs) as Arc, - mount_point, - }) - } - - /// Get the mount point path - pub fn mount_point(&self) -> &Path { - &self.mount_point - } - - /// Translate a sandbox path to a relative path for the SDK - fn translate_to_relative(&self, path: &Path) -> VfsResult { - let path_str = path - .to_str() - .ok_or_else(|| VfsError::InvalidInput("Invalid path".to_string()))?; - - let mount_str = self - .mount_point - .to_str() - .ok_or_else(|| VfsError::InvalidInput("Invalid mount point".to_string()))?; - - // Remove mount point prefix to get relative path - let relative = if path_str == mount_str { - "/" - } else if let Some(rel) = path_str.strip_prefix(&format!("{}/", mount_str)) { - &format!("/{}", rel) - } else { - return Err(VfsError::NotFound); - }; - - Ok(relative.to_string()) - } - - /// Resolve a path to an inode by walking from root - async fn resolve_path(&self, path: &str) -> VfsResult { - if path == "/" { - return Ok(ROOT_INO); - } - - let mut current_ino = ROOT_INO; - for component in path.split('/').filter(|s| !s.is_empty()) { - let stats = self - .fs - .lookup(current_ino, component) - .await - .map_err(|e| VfsError::Other(format!("Failed to lookup: {}", e)))? - .ok_or(VfsError::NotFound)?; - current_ino = stats.ino; - } - - Ok(current_ino) - } - - /// Resolve a path to (parent_ino, name) - fn split_path(path: &str) -> VfsResult<(String, String)> { - if path == "/" { - return Err(VfsError::InvalidInput("Cannot split root path".to_string())); - } - - let path = path.trim_end_matches('/'); - if let Some(pos) = path.rfind('/') { - let parent = if pos == 0 { "/" } else { &path[..pos] }; - let name = &path[pos + 1..]; - Ok((parent.to_string(), name.to_string())) - } else { - Ok(("/".to_string(), path.to_string())) - } - } -} - -#[async_trait::async_trait] -impl Vfs for SqliteVfs { - fn translate_path(&self, path: &Path) -> VfsResult { - // For virtual VFS, we just validate the path is under our mount point - let path_str = path - .to_str() - .ok_or_else(|| VfsError::InvalidInput("Invalid path".to_string()))?; - - let mount_str = self - .mount_point - .to_str() - .ok_or_else(|| VfsError::InvalidInput("Invalid mount point".to_string()))?; - - if path_str.starts_with(mount_str) { - Ok(path.to_path_buf()) - } else { - Err(VfsError::NotFound) - } - } - - fn is_virtual(&self) -> bool { - true - } - - async fn open(&self, path: &Path, flags: i32, _mode: u32) -> VfsResult { - let relative_path = self.translate_to_relative(path)?; - - // Try to resolve the path to get stats - let stats_result = if relative_path == "/" { - self.fs.getattr(ROOT_INO).await - } else { - let (parent_path, name) = Self::split_path(&relative_path)?; - let parent_ino = self.resolve_path(&parent_path).await?; - self.fs.lookup(parent_ino, &name).await - }; - - let stats = stats_result.map_err(|e| VfsError::Other(format!("Failed to stat: {}", e)))?; - - match stats { - Some(stats) => { - if stats.is_directory() { - Ok(Arc::new(SqliteDirectoryOps { - fs: self.fs.clone(), - ino: stats.ino, - path: relative_path, - flags: Mutex::new(flags), - entries: Arc::new(Mutex::new(None)), - position: Arc::new(Mutex::new(0)), - })) - } else { - // If O_TRUNC is set, skip reading the file and use empty data - let data = if flags & libc::O_TRUNC != 0 { - Vec::new() - } else { - // Read file content using open + pread - let file = - self.fs.open(stats.ino, libc::O_RDONLY).await.map_err(|e| { - VfsError::Other(format!("Failed to open file: {}", e)) - })?; - file.pread(0, stats.size as u64) - .await - .map_err(|e| VfsError::Other(format!("Failed to read file: {}", e)))? - }; - Ok(Arc::new(SqliteFileOps { - fs: self.fs.clone(), - ino: stats.ino, - path: relative_path, - data: Arc::new(Mutex::new(data)), - offset: Arc::new(Mutex::new(0)), - flags: Mutex::new(flags), - dirty: Arc::new(Mutex::new(flags & libc::O_TRUNC != 0)), - })) - } - } - None => { - // File doesn't exist - check if O_CREAT is set - if flags & libc::O_CREAT != 0 { - let data = Vec::new(); - - // We don't have an inode yet - use 0 as placeholder - // The actual file will be created on fsync/close - Ok(Arc::new(SqliteFileOps { - fs: self.fs.clone(), - ino: 0, // Will be assigned when created - path: relative_path, - data: Arc::new(Mutex::new(data)), - offset: Arc::new(Mutex::new(0)), - flags: Mutex::new(flags), - dirty: Arc::new(Mutex::new(true)), // Mark as dirty so it gets written on close - })) - } else { - // File doesn't exist and O_CREAT not set - Err(VfsError::NotFound) - } - } - } - } - - async fn stat(&self, path: &Path) -> VfsResult { - let relative_path = self.translate_to_relative(path)?; - - let ino = self.resolve_path(&relative_path).await?; - let stats = self - .fs - .getattr(ino) - .await - .map_err(|e| VfsError::Other(format!("Failed to getattr: {}", e)))? - .ok_or(VfsError::NotFound)?; - - // Use MaybeUninit to construct libc::stat safely - let mut stat: std::mem::MaybeUninit = std::mem::MaybeUninit::zeroed(); - unsafe { - let stat_ptr = stat.as_mut_ptr(); - (*stat_ptr).st_dev = 0; - (*stat_ptr).st_ino = stats.ino as u64; - (*stat_ptr).st_nlink = stats.nlink.into(); - (*stat_ptr).st_mode = stats.mode; - (*stat_ptr).st_uid = stats.uid; - (*stat_ptr).st_gid = stats.gid; - (*stat_ptr).st_rdev = 0; - (*stat_ptr).st_size = stats.size; - (*stat_ptr).st_blksize = 4096; - (*stat_ptr).st_blocks = (stats.size + 4095) / 4096; - (*stat_ptr).st_atime = stats.atime; - (*stat_ptr).st_atime_nsec = 0; - (*stat_ptr).st_mtime = stats.mtime; - (*stat_ptr).st_mtime_nsec = 0; - (*stat_ptr).st_ctime = stats.ctime; - (*stat_ptr).st_ctime_nsec = 0; - Ok(stat.assume_init()) - } - } - - async fn lstat(&self, path: &Path) -> VfsResult { - let relative_path = self.translate_to_relative(path)?; - - // For lstat, we use lookup which doesn't follow symlinks - let stats = if relative_path == "/" { - self.fs - .getattr(ROOT_INO) - .await - .map_err(|e| VfsError::Other(format!("Failed to getattr: {}", e)))? - .ok_or(VfsError::NotFound)? - } else { - let (parent_path, name) = Self::split_path(&relative_path)?; - let parent_ino = self.resolve_path(&parent_path).await?; - self.fs - .lookup(parent_ino, &name) - .await - .map_err(|e| VfsError::Other(format!("Failed to lookup: {}", e)))? - .ok_or(VfsError::NotFound)? - }; - - // Use MaybeUninit to construct libc::stat safely - let mut stat: std::mem::MaybeUninit = std::mem::MaybeUninit::zeroed(); - unsafe { - let stat_ptr = stat.as_mut_ptr(); - (*stat_ptr).st_dev = 0; - (*stat_ptr).st_ino = stats.ino as u64; - (*stat_ptr).st_nlink = stats.nlink.into(); - (*stat_ptr).st_mode = stats.mode; - (*stat_ptr).st_uid = stats.uid; - (*stat_ptr).st_gid = stats.gid; - (*stat_ptr).st_rdev = 0; - (*stat_ptr).st_size = stats.size; - (*stat_ptr).st_blksize = 4096; - (*stat_ptr).st_blocks = (stats.size + 4095) / 4096; - (*stat_ptr).st_atime = stats.atime; - (*stat_ptr).st_atime_nsec = 0; - (*stat_ptr).st_mtime = stats.mtime; - (*stat_ptr).st_mtime_nsec = 0; - (*stat_ptr).st_ctime = stats.ctime; - (*stat_ptr).st_ctime_nsec = 0; - Ok(stat.assume_init()) - } - } - - async fn symlink(&self, target: &Path, linkpath: &Path) -> VfsResult<()> { - let linkpath_rel = self.translate_to_relative(linkpath)?; - let target_str = target - .to_str() - .ok_or_else(|| VfsError::InvalidInput("Invalid target path".to_string()))?; - - let (parent_path, name) = Self::split_path(&linkpath_rel)?; - let parent_ino = self.resolve_path(&parent_path).await?; - - self.fs - .symlink(parent_ino, &name, target_str, 0, 0) - .await - .map_err(|e| { - let err_msg = e.to_string(); - if err_msg.contains("already exists") { - VfsError::AlreadyExists - } else { - VfsError::Other(format!("Failed to create symlink: {}", e)) - } - })?; - - Ok(()) - } - - async fn readlink(&self, path: &Path) -> VfsResult { - let relative_path = self.translate_to_relative(path)?; - - let ino = self.resolve_path(&relative_path).await?; - let target = self - .fs - .readlink(ino) - .await - .map_err(|e| VfsError::Other(format!("Failed to read symlink: {}", e)))? - .ok_or(VfsError::NotFound)?; - - Ok(PathBuf::from(target)) - } - - async fn link(&self, oldpath: &Path, newpath: &Path) -> VfsResult<()> { - let oldpath_rel = self.translate_to_relative(oldpath)?; - let newpath_rel = self.translate_to_relative(newpath)?; - - let old_ino = self.resolve_path(&oldpath_rel).await?; - let (new_parent_path, new_name) = Self::split_path(&newpath_rel)?; - let new_parent_ino = self.resolve_path(&new_parent_path).await?; - - self.fs - .link(old_ino, new_parent_ino, &new_name) - .await - .map_err(|e| { - let err_msg = e.to_string(); - if err_msg.contains("does not exist") { - VfsError::NotFound - } else if err_msg.contains("already exists") { - VfsError::AlreadyExists - } else if err_msg.contains("directory") { - VfsError::PermissionDenied - } else { - VfsError::Other(format!("Failed to create hard link: {}", e)) - } - })?; - - Ok(()) - } -} - -/// File operations for SQLite VFS files -struct SqliteFileOps { - fs: Arc, - ino: i64, - path: String, - data: Arc>>, - offset: Arc>, - flags: Mutex, - dirty: Arc>, -} - -impl SqliteFileOps { - /// Resolve the path and get the inode (for new files) - async fn get_or_create_ino(&self) -> VfsResult { - if self.ino != 0 { - return Ok(self.ino); - } - - // Need to create the file - let (parent_path, name) = SqliteVfs::split_path(&self.path)?; - - // Walk to parent - let mut parent_ino = ROOT_INO; - for component in parent_path.split('/').filter(|s| !s.is_empty()) { - let stats = self - .fs - .lookup(parent_ino, component) - .await - .map_err(|e| VfsError::Other(format!("Failed to lookup: {}", e)))? - .ok_or(VfsError::NotFound)?; - parent_ino = stats.ino; - } - - // Create the file - let (stats, _file) = self - .fs - .create_file(parent_ino, &name, 0o644, 0, 0) - .await - .map_err(|e| VfsError::Other(format!("Failed to create file: {}", e)))?; - - Ok(stats.ino) - } -} - -#[async_trait::async_trait] -impl FileOps for SqliteFileOps { - async fn read(&self, buf: &mut [u8]) -> VfsResult { - let data = self.data.lock().unwrap(); - let mut offset = self.offset.lock().unwrap(); - - let start = *offset as usize; - if start >= data.len() { - return Ok(0); - } - - let end = std::cmp::min(start + buf.len(), data.len()); - let bytes_read = end - start; - buf[..bytes_read].copy_from_slice(&data[start..end]); - *offset += bytes_read as i64; - - Ok(bytes_read) - } - - async fn write(&self, buf: &[u8]) -> VfsResult { - let mut data = self.data.lock().unwrap(); - let mut offset = self.offset.lock().unwrap(); - let flags = *self.flags.lock().unwrap(); - - // Handle O_APPEND: always write at the end of the file - let start = if flags & libc::O_APPEND != 0 { - data.len() - } else { - *offset as usize - }; - - // Extend the buffer if necessary - if start + buf.len() > data.len() { - data.resize(start + buf.len(), 0); - } - - data[start..start + buf.len()].copy_from_slice(buf); - *offset = (start + buf.len()) as i64; - - // Mark as dirty since we modified the data - *self.dirty.lock().unwrap() = true; - - Ok(buf.len()) - } - - async fn seek(&self, offset: i64, whence: i32) -> VfsResult { - let data = self.data.lock().unwrap(); - let mut current_offset = self.offset.lock().unwrap(); - - let new_offset = match whence { - libc::SEEK_SET => offset, - libc::SEEK_CUR => *current_offset + offset, - libc::SEEK_END => data.len() as i64 + offset, - _ => return Err(VfsError::Other("Invalid whence".to_string())), - }; - - if new_offset < 0 { - return Err(VfsError::Other("Invalid offset".to_string())); - } - - *current_offset = new_offset; - Ok(new_offset) - } - - async fn fstat(&self) -> VfsResult { - // Get the actual file stats from the filesystem - let ino = self.get_or_create_ino().await?; - let stats = self - .fs - .getattr(ino) - .await - .map_err(|e| VfsError::Other(format!("Failed to getattr: {}", e)))? - .ok_or(VfsError::NotFound)?; - - let data = self.data.lock().unwrap(); - - // Use MaybeUninit to construct libc::stat safely - let mut stat: std::mem::MaybeUninit = std::mem::MaybeUninit::zeroed(); - unsafe { - let stat_ptr = stat.as_mut_ptr(); - (*stat_ptr).st_dev = 0; - (*stat_ptr).st_ino = stats.ino as u64; - (*stat_ptr).st_nlink = stats.nlink.into(); - (*stat_ptr).st_mode = stats.mode; - (*stat_ptr).st_uid = stats.uid; - (*stat_ptr).st_gid = stats.gid; - (*stat_ptr).st_rdev = 0; - (*stat_ptr).st_size = data.len() as i64; - (*stat_ptr).st_blksize = 4096; - (*stat_ptr).st_blocks = (data.len() as i64 + 4095) / 4096; - (*stat_ptr).st_atime = stats.atime; - (*stat_ptr).st_atime_nsec = 0; - (*stat_ptr).st_mtime = stats.mtime; - (*stat_ptr).st_mtime_nsec = 0; - (*stat_ptr).st_ctime = stats.ctime; - (*stat_ptr).st_ctime_nsec = 0; - Ok(stat.assume_init()) - } - } - - async fn fsync(&self) -> VfsResult<()> { - // For virtual file, sync means write to database - let dirty = *self.dirty.lock().unwrap(); - if !dirty { - return Ok(()); - } - - let data = self.data.lock().unwrap().clone(); - let ino = self.get_or_create_ino().await?; - - // Write the data to the database - let file = self - .fs - .open(ino, libc::O_RDWR) - .await - .map_err(|e| VfsError::Other(format!("Failed to open file: {}", e)))?; - file.pwrite(0, &data) - .await - .map_err(|e| VfsError::Other(format!("Failed to write file: {}", e)))?; - file.truncate(data.len() as u64) - .await - .map_err(|e| VfsError::Other(format!("Failed to truncate file: {}", e)))?; - - // Clear dirty flag after successful write - *self.dirty.lock().unwrap() = false; - - Ok(()) - } - - async fn fdatasync(&self) -> VfsResult<()> { - // For virtual file, same as fsync - self.fsync().await - } - - fn fcntl(&self, cmd: i32, arg: i64) -> VfsResult { - match cmd { - libc::F_GETFL => Ok(self.get_flags() as i64), - libc::F_SETFL => { - self.set_flags(arg as i32)?; - Ok(0) - } - _ => Err(VfsError::Other(format!( - "Unsupported fcntl command: {}", - cmd - ))), - } - } - - fn ioctl(&self, _request: u64, _arg: u64) -> VfsResult { - // Virtual file doesn't support ioctl - Err(VfsError::Other("ioctl not supported".to_string())) - } - - fn as_raw_fd(&self) -> Option { - // No real kernel FD for virtual files - None - } - - async fn close(&self) -> VfsResult<()> { - // Ensure all data is written to the database before closing - self.fsync().await - } - - fn get_flags(&self) -> i32 { - *self.flags.lock().unwrap() - } - - fn set_flags(&self, flags: i32) -> VfsResult<()> { - *self.flags.lock().unwrap() = flags; - Ok(()) - } -} - -/// Type alias for directory entry list: (inode, name, type) -type DirEntryList = Vec<(u64, String, u8)>; - -/// Directory operations for SQLite VFS directories -struct SqliteDirectoryOps { - fs: Arc, - ino: i64, - path: String, - flags: Mutex, - /// Cached directory entries - entries: Arc>>, - /// Current position in the directory listing - position: Arc>, -} - -#[async_trait::async_trait] -impl FileOps for SqliteDirectoryOps { - async fn read(&self, _buf: &mut [u8]) -> VfsResult { - // Cannot read from a directory - Err(VfsError::Other("Is a directory".to_string())) - } - - async fn write(&self, _buf: &[u8]) -> VfsResult { - // Cannot write to a directory - Err(VfsError::Other("Is a directory".to_string())) - } - - async fn seek(&self, _offset: i64, _whence: i32) -> VfsResult { - // Cannot seek in a directory - Err(VfsError::Other("Is a directory".to_string())) - } - - async fn fstat(&self) -> VfsResult { - // Get stats from the filesystem - let stats = self - .fs - .getattr(self.ino) - .await - .map_err(|e| VfsError::Other(format!("Failed to getattr: {}", e)))? - .ok_or(VfsError::NotFound)?; - - // Use MaybeUninit to construct libc::stat safely - let mut stat: std::mem::MaybeUninit = std::mem::MaybeUninit::zeroed(); - unsafe { - let stat_ptr = stat.as_mut_ptr(); - (*stat_ptr).st_dev = 0; - (*stat_ptr).st_ino = stats.ino as u64; - (*stat_ptr).st_nlink = stats.nlink.into(); - (*stat_ptr).st_mode = stats.mode; - (*stat_ptr).st_uid = stats.uid; - (*stat_ptr).st_gid = stats.gid; - (*stat_ptr).st_rdev = 0; - (*stat_ptr).st_size = stats.size; - (*stat_ptr).st_blksize = 4096; - (*stat_ptr).st_blocks = (stats.size + 4095) / 4096; - (*stat_ptr).st_atime = stats.atime; - (*stat_ptr).st_atime_nsec = 0; - (*stat_ptr).st_mtime = stats.mtime; - (*stat_ptr).st_mtime_nsec = 0; - (*stat_ptr).st_ctime = stats.ctime; - (*stat_ptr).st_ctime_nsec = 0; - Ok(stat.assume_init()) - } - } - - async fn fsync(&self) -> VfsResult<()> { - // Nothing to sync for directories - Ok(()) - } - - async fn fdatasync(&self) -> VfsResult<()> { - // Nothing to sync for directories - Ok(()) - } - - fn fcntl(&self, cmd: i32, arg: i64) -> VfsResult { - match cmd { - libc::F_GETFL => Ok(self.get_flags() as i64), - libc::F_SETFL => { - self.set_flags(arg as i32)?; - Ok(0) - } - _ => Err(VfsError::Other(format!( - "Unsupported fcntl command: {}", - cmd - ))), - } - } - - fn ioctl(&self, _request: u64, _arg: u64) -> VfsResult { - // Virtual directory doesn't support ioctl - Err(VfsError::Other("ioctl not supported".to_string())) - } - - fn as_raw_fd(&self) -> Option { - // No real kernel FD for virtual directories - None - } - - async fn close(&self) -> VfsResult<()> { - // Nothing to do when closing a directory - Ok(()) - } - - fn get_flags(&self) -> i32 { - *self.flags.lock().unwrap() - } - - fn set_flags(&self, flags: i32) -> VfsResult<()> { - *self.flags.lock().unwrap() = flags; - Ok(()) - } - - async fn getdents(&self) -> VfsResult { - // Check if we need to populate the entries cache - let needs_populate = { - let entries_lock = self.entries.lock().unwrap(); - entries_lock.is_none() - }; - - if needs_populate { - // Read directory entries from the filesystem using readdir_plus - let dir_entries = self - .fs - .readdir_plus(self.ino) - .await - .map_err(|e| VfsError::Other(format!("Failed to read directory: {}", e)))? - .ok_or(VfsError::NotFound)?; - - // Convert to the format expected by getdents64 - let mut result = Vec::new(); - - // Get current directory stats for "." - let current_stats = self - .fs - .getattr(self.ino) - .await - .map_err(|e| VfsError::Other(format!("Failed to getattr current dir: {}", e)))? - .ok_or(VfsError::NotFound)?; - - // Get parent directory inode for ".." - // Walk the path to find the parent - let parent_ino = if self.path == "/" { - ROOT_INO // Root's parent is itself - } else { - let parent_path = std::path::Path::new(&self.path) - .parent() - .map(|p| p.to_str().unwrap_or("/").to_string()) - .unwrap_or("/".to_string()); - let parent_path = if parent_path.is_empty() { - "/" - } else { - &parent_path - }; - - // Walk to find parent inode - let mut ino = ROOT_INO; - for component in parent_path.split('/').filter(|s| !s.is_empty()) { - if let Some(stats) = self - .fs - .lookup(ino, component) - .await - .map_err(|e| VfsError::Other(format!("Failed to lookup: {}", e)))? - { - ino = stats.ino; - } - } - ino - }; - - result.push((current_stats.ino as u64, ".".to_string(), libc::DT_DIR)); - result.push((parent_ino as u64, "..".to_string(), libc::DT_DIR)); - - for entry in dir_entries { - let d_type = if entry.stats.is_directory() { - libc::DT_DIR - } else if entry.stats.is_symlink() { - libc::DT_LNK - } else { - libc::DT_REG - }; - result.push((entry.stats.ino as u64, entry.name, d_type)); - } - - // Store the results - let mut entries_lock = self.entries.lock().unwrap(); - *entries_lock = Some(result); - } - - // Get the current position and return entries - let mut position = self.position.lock().unwrap(); - let entries_lock = self.entries.lock().unwrap(); - let all_entries = entries_lock.as_ref().unwrap(); - - if *position >= all_entries.len() { - // No more entries - return empty to signal EOF - Ok(Vec::new()) - } else { - // Return remaining entries and update position - let remaining = all_entries[*position..].to_vec(); - *position = all_entries.len(); - Ok(remaining) - } - } -} diff --git a/scripts/gate.sh b/scripts/gate.sh new file mode 100755 index 00000000..bcb27b45 --- /dev/null +++ b/scripts/gate.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# +# Honest local milestone gate. +# +# This is the single developer and CI entrypoint for the M1 gate. It fails on +# every command failure, runs the shell suite in strict mode so SKIP is red on +# the designated runner, and keeps the codex benchmark out of CI. +set -Eeuo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$REPO_ROOT" + +AGENTFS_BIN="${AGENTFS_BIN:-$REPO_ROOT/target/release/agentfs}" +RUST_TOOLCHAIN="${RUST_TOOLCHAIN:-nightly}" +SHELL_TIMEOUT="${AGENTFS_GATE_SHELL_TIMEOUT:-900}" +PHASE8_TIMEOUT="${AGENTFS_GATE_PHASE8_TIMEOUT:-20}" + +# Pin TMPDIR to a per-run scratch dir cleaned on exit: turso_core 0.5.3 leaks +# /tmp/tursodb-ephemeral-* sort-spill files (vdbe/execute.rs:10096 never +# unlinks them), so dependency litter must not accumulate on the host. +GATE_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/agentfs-gate.XXXXXX")" +trap 'rm -rf "$GATE_TMPDIR"' EXIT +export TMPDIR="$GATE_TMPDIR" TMP="$GATE_TMPDIR" TEMP="$GATE_TMPDIR" +export PYTHONDONTWRITEBYTECODE=1 + +run() { + printf '\n==> %s\n' "$*" + "$@" +} + +run_cargo() { + printf '\n==> cargo +%s %s\n' "$RUST_TOOLCHAIN" "$*" + cargo "+$RUST_TOOLCHAIN" "$@" +} + +run_cargo fmt --all -- --check +run_cargo clippy --workspace --all-targets -- -D warnings +run_cargo test --workspace +run_cargo build --release --workspace --bins + +printf '\n==> crates/agentfs-cli/tests/all.sh\n' +AGENTFS_GATE_STRICT=1 \ +AGENTFS_GATE_ALLOWED_SKIPS="${AGENTFS_GATE_ALLOWED_SKIPS:-}" \ +AGENTFS_BIN="$AGENTFS_BIN" \ +CORRUPTION_TORTURE_WORKERS="${CORRUPTION_TORTURE_WORKERS:-4}" \ +CORRUPTION_TORTURE_ITERATIONS="${CORRUPTION_TORTURE_ITERATIONS:-3}" \ +CORRUPTION_TORTURE_TIMEOUT="${CORRUPTION_TORTURE_TIMEOUT:-120}" \ +CORRUPTION_TORTURE_TEARDOWN_TIMEOUT="${CORRUPTION_TORTURE_TEARDOWN_TIMEOUT:-10}" \ +timeout "$SHELL_TIMEOUT" crates/agentfs-cli/tests/all.sh + +# Phase 8 smoke is the top-level python gate; the noopen/flush/base-drift +# coherence harnesses run inside it (M7 scripts consolidation). +run python3 scripts/validation/phase8-validation.py \ + --smoke \ + --timeout "$PHASE8_TIMEOUT" \ + --agentfs-bin "$AGENTFS_BIN" \ + --output /tmp/vfs-val/phase8.json + +run scripts/validation/consistency-canon.sh + +printf '\nHonest gate passed with AGENTFS_BIN=%s\n' "$AGENTFS_BIN" diff --git a/scripts/update-version.py b/scripts/update-version.py index 2b73ef59..d076f66c 100755 --- a/scripts/update-version.py +++ b/scripts/update-version.py @@ -1,433 +1,91 @@ #!/usr/bin/env python3 """ -Update version numbers across all AgentFS components. +Update the Rust workspace version. -Usage: - python scripts/update-version.py 0.1.0-pre.1 +Rust crates inherit their version from the root [workspace.package] table, so +there is no per-crate Cargo.toml or per-crate Cargo.lock sync job anymore. +This script updates the single workspace version and refreshes the root lockfile. """ +from __future__ import annotations + import argparse -import json import re import subprocess import sys from pathlib import Path -from typing import List + +SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$") +WORKSPACE_VERSION_RE = re.compile( + r'(^\[workspace\.package\]\s*(?:(?!^\[).)*?^version\s*=\s*)"[^"]*"', + re.MULTILINE | re.DOTALL, +) def parse_version(version: str) -> str: - """Validate and normalize version string.""" - # Basic semver validation (supports pre-release versions) - pattern = r'^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$' - if not re.match(pattern, version): + if not SEMVER_RE.match(version): raise ValueError( - f"Invalid version format: {version}. " - "Expected format: X.Y.Z or X.Y.Z-pre.N" + f"Invalid version format: {version}. Expected X.Y.Z or X.Y.Z-pre.N" ) return version -def update_cargo_toml(file_path: Path, new_version: str) -> bool: - """Update version in a Cargo.toml file.""" - try: - content = file_path.read_text() - - # Find and replace the version line under [package] - # Match version = "..." with proper spacing - pattern = r'(^\[package\].*?^version\s*=\s*)"[^"]*"' - replacement = rf'\1"{new_version}"' - - new_content = re.sub( - pattern, - replacement, - content, - count=1, - flags=re.MULTILINE | re.DOTALL - ) - - if new_content == content: - print(f"Warning: No version field found in {file_path}") - return False - - file_path.write_text(new_content) - print(f" Updated {file_path.relative_to(file_path.parents[3])}") - return True - - except Exception as e: - print(f"Error updating {file_path}: {e}", file=sys.stderr) - return False - - -def update_package_json(file_path: Path, new_version: str) -> bool: - """Update version in a package.json file.""" - try: - with open(file_path, 'r') as f: - data = json.load(f) - - if 'version' not in data: - print(f"Warning: No version field found in {file_path}") - return False - - data['version'] = new_version - - with open(file_path, 'w') as f: - json.dump(data, f, indent=2) - f.write('\n') # Add trailing newline - - print(f" Updated {file_path.relative_to(file_path.parents[3])}") - return True - - except Exception as e: - print(f"Error updating {file_path}: {e}", file=sys.stderr) - return False - - -def update_pyproject_toml(file_path: Path, new_version: str) -> bool: - """Update version in a pyproject.toml file.""" - try: - content = file_path.read_text() - - # Find and replace the version line under [project] - # Match version = "..." with proper spacing - pattern = r'(^\[project\].*?^version\s*=\s*)"[^"]*"' - replacement = rf'\1"{new_version}"' - - new_content = re.sub( - pattern, - replacement, - content, - count=1, - flags=re.MULTILINE | re.DOTALL - ) - - if new_content == content: - print(f"Warning: No version field found in {file_path}") - return False - - file_path.write_text(new_content) - print(f" Updated {file_path.relative_to(file_path.parents[3])}") - return True - - except Exception as e: - print(f"Error updating {file_path}: {e}", file=sys.stderr) - return False - -def update_py_init(file_path: Path, new_version: str) -> bool: - """Update version in a __init__.py file.""" - try: - content = file_path.read_text() - - # Find and replace the version line under [project] - # Match version = "..." with proper spacing - pattern = r'__version__ = "[^"]+"' - replacement = rf'__version__ = "{new_version}"' - - new_content = re.sub( - pattern, - replacement, - content, - count=1, - flags=re.MULTILINE | re.DOTALL - ) - - if new_content == content: - print(f"Warning: No version field found in {file_path}") - return False - - file_path.write_text(new_content) - print(f" Updated {file_path.relative_to(file_path.parents[3])}") - return True - - except Exception as e: - print(f"Error updating {file_path}: {e}", file=sys.stderr) - return False - - -def update_cargo_lock(crate_dir: Path) -> bool: - """Update Cargo.lock by regenerating it in the crate directory.""" - try: - # Use cargo generate-lockfile to force regeneration of Cargo.lock - # This ensures path dependencies get their versions updated - result = subprocess.run( - ['cargo', 'generate-lockfile'], - cwd=crate_dir, - capture_output=True, - text=True - ) - - if result.returncode == 0: - return True - else: - print(f"Error updating Cargo.lock: {result.stderr}", file=sys.stderr) - return False - - except Exception as e: - print(f"Error updating Cargo.lock: {e}", file=sys.stderr) - return False - - -def update_package_lock(package_dir: Path) -> bool: - """Update package-lock.json by running npm install.""" - try: - result = subprocess.run( - ['npm', 'install'], - cwd=package_dir, - capture_output=True, - text=True - ) - - if result.returncode == 0: - return True - else: - print(f"Error updating package-lock.json: {result.stderr}", file=sys.stderr) - return False - - except Exception as e: - print(f"Error updating package-lock.json: {e}", file=sys.stderr) - return False - - -def update_uv_lock(package_dir: Path) -> bool: - """Update uv.lock by running uv lock.""" - try: - result = subprocess.run( - ['uv', 'lock'], - cwd=package_dir, - capture_output=True, - text=True - ) - - if result.returncode == 0: - return True - else: - print(f"Error updating uv.lock: {result.stderr}", file=sys.stderr) - return False - - except Exception as e: - print(f"Error updating uv.lock: {e}", file=sys.stderr) - return False - - -def git_commit_and_tag(root: Path, version: str, components: list) -> bool: - """Create a git commit and tag for the version update.""" - try: - print("\nCreating git commit and tag...") - - # Stage only the files we modified - files_to_add = [] - for component in components: - # Add version file (Cargo.toml, package.json, or pyproject.toml) - files_to_add.append(str(component['version_file'].relative_to(root))) - - # Add lock file (Cargo.lock, package-lock.json, or uv.lock) - version_file_str = str(component['version_file']) - if 'Cargo.toml' in version_file_str: - lock_file = component['lock_dir'] / 'Cargo.lock' - elif 'pyproject.toml' in version_file_str: - lock_file = component['lock_dir'] / 'uv.lock' - else: - lock_file = component['lock_dir'] / 'package-lock.json' - - if lock_file.exists(): - files_to_add.append(str(lock_file.relative_to(root))) - - result = subprocess.run( - ['git', 'add'] + files_to_add, - cwd=root, - capture_output=True, - text=True - ) - - if result.returncode != 0: - print(f"Error staging changes: {result.stderr}", file=sys.stderr) - return False - - # Commit - commit_message = f"AgentFS {version}" - result = subprocess.run( - ['git', 'commit', '-m', commit_message], - cwd=root, - capture_output=True, - text=True - ) - - if result.returncode != 0: - print(f"Error creating commit: {result.stderr}", file=sys.stderr) - return False - - print(f"✓ Created commit: {commit_message}") - - # Create tag - tag_name = f"v{version}" - result = subprocess.run( - ['git', 'tag', tag_name], - cwd=root, - capture_output=True, - text=True - ) - - if result.returncode != 0: - print(f"Error creating tag: {result.stderr}", file=sys.stderr) - return False - - print(f"✓ Created tag: {tag_name}") - - return True - - except Exception as e: - print(f"Error in git operations: {e}", file=sys.stderr) - return False - - def find_project_root() -> Path: - """Find the project root directory.""" current = Path(__file__).resolve().parent - # Go up until we find the root (where cli, sdk, sandbox directories are) while current.parent != current: - if (current / 'cli').exists() and (current / 'sdk').exists(): + if (current / "Cargo.toml").is_file() and (current / "cli").is_dir(): return current current = current.parent raise RuntimeError("Could not find project root") -def main(): - parser = argparse.ArgumentParser( - description='Update version numbers across all AgentFS components' - ) - parser.add_argument( - 'version', - help='Version number (e.g., 0.1.0 or 0.1.0-pre.1)' - ) - parser.add_argument( - '--dry-run', - action='store_true', - help='Show what would be updated without making changes' +def update_workspace_version(root: Path, version: str, dry_run: bool) -> bool: + manifest = root / "Cargo.toml" + content = manifest.read_text() + new_content, count = WORKSPACE_VERSION_RE.subn(rf'\1"{version}"', content, count=1) + if count != 1: + raise RuntimeError("Could not find [workspace.package] version in root Cargo.toml") + if new_content == content: + print(f"Workspace version already {version}") + return False + if dry_run: + print(f"Would update {manifest.relative_to(root)} to {version}") + else: + manifest.write_text(new_content) + print(f"Updated {manifest.relative_to(root)} to {version}") + return True + + +def refresh_lockfile(root: Path, dry_run: bool) -> None: + if dry_run: + print("Would run: cargo generate-lockfile") + return + result = subprocess.run( + ["cargo", "generate-lockfile"], + cwd=root, + text=True, ) + if result.returncode != 0: + raise RuntimeError("cargo generate-lockfile failed") + +def main() -> int: + parser = argparse.ArgumentParser(description="Update the AgentFS workspace version") + parser.add_argument("version", help="Version number, for example 0.6.5 or 0.7.0-pre.1") + parser.add_argument("--dry-run", action="store_true", help="Print changes without writing files") args = parser.parse_args() try: version = parse_version(args.version) - except ValueError as e: - print(f"Error: {e}", file=sys.stderr) - sys.exit(1) - - # Find project root - try: root = find_project_root() - except RuntimeError as e: - print(f"Error: {e}", file=sys.stderr) - sys.exit(1) - - print(f"Updating version to: {version}") - print(f"Project root: {root}\n") - - # Define all components to update - components = [ - # Rust crates - { - 'version_file': root / 'cli' / 'Cargo.toml', - 'version_func': update_cargo_toml, - 'lock_dir': root / 'cli', - 'lock_func': update_cargo_lock, - 'name': 'cli' - }, - { - 'version_file': root / 'sandbox' / 'Cargo.toml', - 'version_func': update_cargo_toml, - 'lock_dir': root / 'sandbox', - 'lock_func': update_cargo_lock, - 'name': 'sandbox' - }, - { - 'version_file': root / 'sdk' / 'rust' / 'Cargo.toml', - 'version_func': update_cargo_toml, - 'lock_dir': root / 'sdk' / 'rust', - 'lock_func': update_cargo_lock, - 'name': 'sdk/rust' - }, - # TypeScript SDK - { - 'version_file': root / 'sdk' / 'typescript' / 'package.json', - 'version_func': update_package_json, - 'lock_dir': root / 'sdk' / 'typescript', - 'lock_func': update_package_lock, - 'name': 'sdk/typescript' - }, - # Python SDK: pyproject.toml - { - 'version_file': root / 'sdk' / 'python' / 'pyproject.toml', - 'version_func': update_pyproject_toml, - 'lock_dir': root / 'sdk' / 'python', - 'lock_func': update_uv_lock, - 'name': 'sdk/python' - }, - # Python SDK: __init__.py - { - 'version_file': root / 'sdk' / 'python' / 'agentfs_sdk' / '__init__.py', - 'version_func': update_py_init, - 'lock_dir': root / 'sdk' / 'python', - 'lock_func': lambda _: True, - 'name': 'sdk/python' - }, - ] - - if args.dry_run: - print("DRY RUN - No files will be modified\n") - for component in components: - if component['version_file'].exists(): - print(f"Would update: {component['version_file'].relative_to(root)}") - print(f" and lock file in {component['name']}/") - else: - print(f"Warning: File not found: {component['version_file'].relative_to(root)}") - print("\nWould also:") - print(f" - Create git commit: 'AgentFS {version}'") - print(f" - Create git tag: 'v{version}'") - sys.exit(0) - - # Update all components - all_success = True - - for component in components: - version_file = component['version_file'] - if not version_file.exists(): - print(f"Warning: File not found: {version_file.relative_to(root)}") - all_success = False - continue - - # Update version file - if component['version_func'](version_file, version): - # Update lock file - if component['lock_func'](component['lock_dir']): - print(f"✓ Updated {component['name']} lock file") - else: - all_success = False - else: - all_success = False - - if not all_success: - print("\n❌ Some files failed to update", file=sys.stderr) - sys.exit(1) - - # Update Cargo.lock files again to pick up new versions of workspace dependencies - # (sandbox and cli both depend on sdk/rust which was updated after their initial lock update) - print("\nUpdating Cargo.lock files to pick up new dependency versions...") - for crate_name in ['sandbox', 'cli']: - if update_cargo_lock(root / crate_name): - print(f"✓ Updated {crate_name}/Cargo.lock with new dependency versions") - else: - print(f"❌ Failed to update {crate_name}/Cargo.lock", file=sys.stderr) - sys.exit(1) - - print("\n✅ All files and lock files updated successfully!") - - # Create git commit and tag - if not git_commit_and_tag(root, version, components): - print("\n❌ Git operations failed", file=sys.stderr) - sys.exit(1) - - print("\n✅ Version update complete! Don't forget to push the commit and tag.") + changed = update_workspace_version(root, version, args.dry_run) + if changed: + refresh_lockfile(root, args.dry_run) + except Exception as exc: + print(f"Error: {exc}", file=sys.stderr) + return 1 + return 0 -if __name__ == '__main__': - main() +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/validation/archive/README.md b/scripts/validation/archive/README.md new file mode 100644 index 00000000..15fe20aa --- /dev/null +++ b/scripts/validation/archive/README.md @@ -0,0 +1,17 @@ +# Archived validation scripts + +Historical one-off validators superseded by the current gate stack. They are +kept for archaeology, are NOT wired into `scripts/gate.sh`, CI, or +`phase8-validation.py`, and are not maintained against the current tree +(several still reference the pre-workspace `cli/` layout). + +The current programmatic gate is `scripts/validation/phase8-validation.py` +(invoked by `scripts/gate.sh`); see `docs/TESTING.md`. + +| Script | Why archived (evidence: research/scripts-docs-sweep.md) | +|---|---| +| `phase0.sh` | Phase 0 fork-governance + synthetic baseline wrapper; superseded by direct `workload-baseline.py` use and the current gates. | +| `phase6-validation.py` | Phase 6 orchestrator; its factory/read/large-edit/no-real-write legs are covered by `phase7-validation.py` and `phase8-validation.py`. | +| `phase65-validation.py` | Phase 6.5 read fast-path orchestrator; Phase 8 calls `base-read-benchmark.py` and `fuse-serialization-stress.py` directly. | +| `check-fork-governance.sh` | Origin-remote governance check from the fork-era workflow; not CI-wired and no longer an active local policy. `phase0.sh` invokes it dirname-relative, so both live here together. | +| `backend-risk-spike.py` | Turso 0.5.x upgrade decision spike; the workspace has shipped on Turso 0.5 since before the restructure, so this is historical evidence rather than an active probe. | diff --git a/scripts/validation/backend-risk-spike.py b/scripts/validation/archive/backend-risk-spike.py similarity index 97% rename from scripts/validation/backend-risk-spike.py rename to scripts/validation/archive/backend-risk-spike.py index cff03c32..62e22fd7 100755 --- a/scripts/validation/backend-risk-spike.py +++ b/scripts/validation/archive/backend-risk-spike.py @@ -339,13 +339,13 @@ def main(argv: list[str]) -> int: }, }, "recommended_validation_commands": [ - "cargo test --manifest-path sdk/rust/Cargo.toml", - "cargo test --manifest-path cli/Cargo.toml", - "cargo test --manifest-path cli/Cargo.toml --no-default-features", - "cli/tests/all.sh", + "cargo +nightly test -p agentfs-core", + "cargo +nightly test -p agentfs-cli", + "cargo +nightly test -p agentfs-cli --no-default-features", + "crates/agentfs-cli/tests/all.sh", "scripts/validation/phase0.sh", - "scripts/validation/replay/replay_workload.py --agentfs-bin cli/target/debug/agentfs /path/to/replay.jsonl", - "scripts/validation/posix/run-pjdfstest.sh --profile phase45-ci --agentfs-bin \"$PWD/cli/target/debug/agentfs\" --pjdfstest-dir /path/to/pjdfstest", + "scripts/validation/replay/replay_workload.py --agentfs-bin target/debug/agentfs /path/to/replay.jsonl", + "scripts/validation/posix/run-pjdfstest.sh --profile phase45-ci --agentfs-bin \"$PWD/target/debug/agentfs\" --pjdfstest-dir /path/to/pjdfstest", ], "decision": { "status": args.decision_status, diff --git a/scripts/validation/check-fork-governance.sh b/scripts/validation/archive/check-fork-governance.sh similarity index 100% rename from scripts/validation/check-fork-governance.sh rename to scripts/validation/archive/check-fork-governance.sh diff --git a/scripts/validation/phase0.sh b/scripts/validation/archive/phase0.sh similarity index 100% rename from scripts/validation/phase0.sh rename to scripts/validation/archive/phase0.sh diff --git a/scripts/validation/phase6-validation.py b/scripts/validation/archive/phase6-validation.py similarity index 100% rename from scripts/validation/phase6-validation.py rename to scripts/validation/archive/phase6-validation.py diff --git a/scripts/validation/phase65-validation.py b/scripts/validation/archive/phase65-validation.py similarity index 100% rename from scripts/validation/phase65-validation.py rename to scripts/validation/archive/phase65-validation.py diff --git a/scripts/validation/bench-compare.py b/scripts/validation/bench-compare.py new file mode 100755 index 00000000..ba17649c --- /dev/null +++ b/scripts/validation/bench-compare.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +"""Compare two median-of-5 git workload benchmark JSON files. + +Exit status is part of the contract: 0 means every phase is within band, 1 +means at least one phase is red, and 2 means either input is malformed. The +baseline and candidate phase sets must match exactly; a phase-set change is a +format error so harness changes force an explicit rebaseline decision. +""" + +from __future__ import annotations + +import argparse +import json +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Any + + +RELATIVE_RED_THRESHOLD = 0.05 +ABSOLUTE_RED_THRESHOLD_SECONDS = 0.010 +EXPECTED_ITERATIONS = 5 + + +class BenchmarkFormatError(ValueError): + """Raised when a benchmark JSON file does not match the perf contract.""" + + +@dataclass(frozen=True) +class PhaseMedian: + phase: str + agentfs_seconds: float + count: int + + +@dataclass(frozen=True) +class PhaseComparison: + phase: str + baseline_seconds: float + candidate_seconds: float + delta_seconds: float + relative_delta: float + + @property + def is_red(self) -> bool: + return ( + self.relative_delta > RELATIVE_RED_THRESHOLD + and self.delta_seconds > ABSOLUTE_RED_THRESHOLD_SECONDS + ) + + +def load_json(path: Path) -> dict[str, Any]: + try: + payload = json.loads(path.read_text()) + except FileNotFoundError as exc: + raise BenchmarkFormatError(f"{path}: file not found") from exc + except json.JSONDecodeError as exc: + raise BenchmarkFormatError(f"{path}: invalid JSON: {exc}") from exc + if not isinstance(payload, dict): + raise BenchmarkFormatError(f"{path}: top-level JSON must be an object") + return payload + + +def require_number(value: Any, path: str) -> float: + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise BenchmarkFormatError(f"{path}: expected number, got {type(value).__name__}") + return float(value) + + +def require_int(value: Any, path: str) -> int: + if isinstance(value, bool) or not isinstance(value, int): + raise BenchmarkFormatError(f"{path}: expected integer, got {type(value).__name__}") + return value + + +def parse_phase_medians(payload: dict[str, Any], source: Path) -> dict[str, PhaseMedian]: + iterations = require_int(payload.get("iterations"), f"{source}: iterations") + if iterations != EXPECTED_ITERATIONS: + raise BenchmarkFormatError( + f"{source}: expected iterations={EXPECTED_ITERATIONS}, got {iterations}" + ) + + returncodes = payload.get("iteration_returncodes") + if not isinstance(returncodes, list): + raise BenchmarkFormatError(f"{source}: iteration_returncodes must be a list") + if len(returncodes) != EXPECTED_ITERATIONS: + raise BenchmarkFormatError( + f"{source}: expected {EXPECTED_ITERATIONS} iteration return codes, got {len(returncodes)}" + ) + bad_returncodes = [code for code in returncodes if code != 0] + if bad_returncodes: + raise BenchmarkFormatError( + f"{source}: benchmark has non-zero iteration return codes: {bad_returncodes}" + ) + + phases = payload.get("phases") + if not isinstance(phases, dict) or not phases: + raise BenchmarkFormatError(f"{source}: phases must be a non-empty object") + + parsed: dict[str, PhaseMedian] = {} + for phase, phase_payload in phases.items(): + if not isinstance(phase, str): + raise BenchmarkFormatError(f"{source}: phase name must be a string") + if not isinstance(phase_payload, dict): + raise BenchmarkFormatError(f"{source}: phases.{phase} must be an object") + agentfs = phase_payload.get("agentfs_seconds") + if not isinstance(agentfs, dict): + raise BenchmarkFormatError( + f"{source}: phases.{phase}.agentfs_seconds must be an object" + ) + count = require_int(agentfs.get("count"), f"{source}: phases.{phase}.agentfs_seconds.count") + if count != EXPECTED_ITERATIONS: + raise BenchmarkFormatError( + f"{source}: phases.{phase}.agentfs_seconds.count expected " + f"{EXPECTED_ITERATIONS}, got {count}" + ) + median = require_number( + agentfs.get("median"), + f"{source}: phases.{phase}.agentfs_seconds.median", + ) + parsed[phase] = PhaseMedian(phase=phase, agentfs_seconds=median, count=count) + + return parsed + + +def compare_phase_medians( + baseline: dict[str, PhaseMedian], + candidate: dict[str, PhaseMedian], +) -> list[PhaseComparison]: + missing_from_candidate = sorted(set(baseline) - set(candidate)) + missing_from_baseline = sorted(set(candidate) - set(baseline)) + if missing_from_candidate or missing_from_baseline: + details: list[str] = [] + if missing_from_candidate: + details.append(f"missing from candidate: {', '.join(missing_from_candidate)}") + if missing_from_baseline: + details.append(f"missing from baseline: {', '.join(missing_from_baseline)}") + raise BenchmarkFormatError("phase sets differ: " + "; ".join(details)) + + comparisons: list[PhaseComparison] = [] + for phase in sorted(baseline): + base = baseline[phase].agentfs_seconds + cand = candidate[phase].agentfs_seconds + if base <= 0: + raise BenchmarkFormatError( + f"baseline phase {phase} has non-positive median {base}" + ) + delta = cand - base + comparisons.append( + PhaseComparison( + phase=phase, + baseline_seconds=base, + candidate_seconds=cand, + delta_seconds=delta, + relative_delta=delta / base, + ) + ) + return comparisons + + +def format_ms(seconds: float) -> str: + return f"{seconds * 1000.0:.1f}" + + +def format_percent(value: float) -> str: + return f"{value * 100.0:+.1f}%" + + +def render_human(comparisons: list[PhaseComparison]) -> str: + lines = [ + "Perf contract: red iff candidate median is >5% slower AND >10ms slower.", + "", + "| Phase | Baseline ms | Candidate ms | Delta ms | Relative | Result |", + "| --- | ---: | ---: | ---: | ---: | --- |", + ] + for item in comparisons: + result = "RED" if item.is_red else "OK" + lines.append( + f"| {item.phase} | {format_ms(item.baseline_seconds)} | " + f"{format_ms(item.candidate_seconds)} | {format_ms(item.delta_seconds)} | " + f"{format_percent(item.relative_delta)} | {result} |" + ) + red_count = sum(1 for item in comparisons if item.is_red) + lines.extend( + [ + "", + f"Summary: {red_count} red phase(s), {len(comparisons) - red_count} within band.", + ] + ) + return "\n".join(lines) + + +def render_json(comparisons: list[PhaseComparison]) -> str: + payload = { + "rule": { + "relative_slower_than": RELATIVE_RED_THRESHOLD, + "absolute_slower_than_seconds": ABSOLUTE_RED_THRESHOLD_SECONDS, + "iterations": EXPECTED_ITERATIONS, + }, + "red_phases": [item.phase for item in comparisons if item.is_red], + "phases": [ + { + "phase": item.phase, + "baseline_seconds": item.baseline_seconds, + "candidate_seconds": item.candidate_seconds, + "delta_seconds": item.delta_seconds, + "relative_delta": item.relative_delta, + "red": item.is_red, + } + for item in comparisons + ], + } + return json.dumps(payload, indent=2, sort_keys=True) + + +def parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Compare two git-workload-benchmark-multi.py aggregate JSON files " + "using the VFS perf contract." + ), + epilog=( + "Exit codes: 0 = all phases within band; 1 = one or more red phases; " + "2 = malformed input, including mismatched phase sets. Phase sets " + "are strict by design so adding or removing a benchmark phase forces " + "a deliberate rebaseline." + ), + ) + parser.add_argument("baseline_json", type=Path, help="M1 baseline aggregate JSON") + parser.add_argument("candidate_json", type=Path, help="candidate aggregate JSON") + parser.add_argument( + "--json", + action="store_true", + help="emit machine-readable JSON instead of a Markdown table", + ) + return parser.parse_args(argv) + + +def main(argv: list[str]) -> int: + args = parse_args(argv) + try: + baseline = parse_phase_medians(load_json(args.baseline_json), args.baseline_json) + candidate = parse_phase_medians(load_json(args.candidate_json), args.candidate_json) + comparisons = compare_phase_medians(baseline, candidate) + except BenchmarkFormatError as exc: + print(f"bench-compare: {exc}", file=sys.stderr) + return 2 + + if args.json: + print(render_json(comparisons)) + else: + print(render_human(comparisons)) + + return 1 if any(item.is_red for item in comparisons) else 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/scripts/validation/consistency-canon.sh b/scripts/validation/consistency-canon.sh new file mode 100755 index 00000000..5d908edd --- /dev/null +++ b/scripts/validation/consistency-canon.sh @@ -0,0 +1,315 @@ +#!/usr/bin/env bash +# +# Consistency-canon census (architecture.md section 7). +# +# Mechanically checks the structural canon rules and prints one PASS/FAIL row +# per rule. Exit 0 only when every row passes. Wired into scripts/gate.sh as +# a gate step (VAL-CONS-014). +set -Eeuo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +cd "$REPO_ROOT" + +FAILURES=0 + +row() { + status="$1" + name="$2" + detail="$3" + printf '%-4s %-24s %s\n' "$status" "$name" "$detail" + if [ "$status" = "FAIL" ]; then + FAILURES=$((FAILURES + 1)) + fi +} + +check_python() { + name="$1" + shift + if detail="$(python3 "$@" 2>&1)"; then + row PASS "$name" "$detail" + else + row FAIL "$name" "$detail" + fi +} + +# --- crate DAG ------------------------------------------------------------- +check_python "dag" - <<'PY' +import json, subprocess, sys + +expected = { + "agentfs-cli": {"agentfs-core", "agentfs-mount"}, + "agentfs-core": set(), + "agentfs-fuse": {"agentfs-core"}, + "agentfs-nfs": {"agentfs-core"}, + "agentfs-mount": {"agentfs-core", "agentfs-fuse", "agentfs-nfs"}, +} + +meta = json.loads( + subprocess.run( + ["cargo", "metadata", "--format-version=1", "--no-deps"], + check=True, text=True, stdout=subprocess.PIPE, + ).stdout +) +packages = {pkg["name"]: pkg for pkg in meta["packages"]} +if set(packages) != set(expected): + print(f"workspace members {sorted(packages)} != expected {sorted(expected)}") + sys.exit(1) +for name, wanted in expected.items(): + actual = { + dep["name"] for dep in packages[name]["dependencies"] + if dep["name"].startswith("agentfs-") + } + if actual != wanted: + print(f"{name} first-party deps {sorted(actual)} != expected {sorted(wanted)}") + sys.exit(1) +print("five-crate workspace; first-party edges match architecture.md section 3") +PY + +# --- sealed transport surfaces ---------------------------------------------- +check_python "sealed-surfaces" - <<'PY' +import re, sys +from pathlib import Path + +problems = [] + +fuse = Path("crates/agentfs-fuse/src/lib.rs").read_text() +fuse_pubs = re.findall(r"^pub .*$", fuse, re.M) +if fuse_pubs != ["pub use adapter::{mount, FuseMountOptions, SessionHandle};"]: + problems.append(f"fuse lib.rs pub surface drifted: {fuse_pubs}") +if re.search(r"^pub mod", fuse, re.M): + problems.append("fuse lib.rs exposes a pub mod") + +nfs = Path("crates/agentfs-nfs/src/lib.rs").read_text() +nfs_items = re.findall(r"^pub (?:async )?(?:struct|fn|enum|trait|mod|use) (\w+)", nfs, re.M) +if sorted(nfs_items) != ["NfsServeOptions", "ServerHandle", "serve"]: + problems.append(f"nfs lib.rs pub surface drifted: {sorted(nfs_items)}") +if re.search(r"^pub mod", nfs, re.M): + problems.append("nfs lib.rs exposes a pub mod") + +if problems: + print("; ".join(problems)) + sys.exit(1) +print("fuse={mount, FuseMountOptions, SessionHandle}; nfs={serve, NfsServeOptions, ServerHandle}") +PY + +# --- shared cfg(test)-aware scanner used by the next three checks ----------- +export CANON_SCAN_HELPER=" +import re + +def nontest_lines(path): + '''Yield (lineno, line) for lines outside cfg(test)/cfg(all(test, ..)) + module blocks, tracked by brace depth from the attribute.''' + lines = path.read_text(errors='replace').splitlines() + in_test = False + depth = 0 + pending_attr = False + for lineno, line in enumerate(lines, 1): + if not in_test and re.match(r'\s*#\[cfg\((all\()?test\b', line): + pending_attr = True + continue + if pending_attr: + if '{' in line: + in_test = True + depth = line.count('{') - line.count('}') + pending_attr = False + if depth <= 0: + in_test = False + continue + if re.match(r'\s*(#\[|mod\s+\w+\s*;|//)', line) or not line.strip(): + # attribute stack, sibling-module declaration, or comment + if re.match(r'\s*mod\s+\w+\s*;', line): + pending_attr = False + continue + pending_attr = False + if in_test: + depth += line.count('{') - line.count('}') + if depth <= 0: + in_test = False + continue + yield lineno, line + +" + +# --- line-count cap ---------------------------------------------------------- +check_python "line-count-cap" - <<'PY' +import os, re, sys +from pathlib import Path + +exec(os.environ["CANON_SCAN_HELPER"]) + +CAP = 2500 +offenders = [] +for path in sorted(Path("crates").rglob("*.rs")): + parts = path.parts + if "target" in parts: + continue + if path.name == "tests.rs" or "tests" in parts: + continue + code = sum( + 1 for _, line in nontest_lines(path) + if line.strip() and not line.strip().startswith("//") + ) + if code > CAP: + offenders.append(f"{path}:{code}") +if offenders: + print(f"non-test code lines over {CAP}: {', '.join(offenders)}") + sys.exit(1) +print(f"no production file over {CAP} non-test code lines") +PY + +# --- tracing-only logging ---------------------------------------------------- +check_python "tracing-only-logging" - <<'PY' +import os, re, sys +from pathlib import Path + +exec(os.environ["CANON_SCAN_HELPER"]) + +# eprintln!/println! are user-facing CLI output only: cmd/, the single +# reporter in main.rs, plus build scripts (canon section 7 item 2). +def allowed(path): + p = str(path) + return ( + p.startswith("crates/agentfs-cli/src/cmd/") + or p == "crates/agentfs-cli/src/main.rs" + or path.name == "build.rs" + ) + +offenders = [] +for path in sorted(Path("crates").rglob("*.rs")): + parts = path.parts + if "target" in parts or path.name == "tests.rs" or "tests" in parts: + continue + if allowed(path): + continue + for lineno, line in nontest_lines(path): + if re.search(r"\b(eprintln!|println!|eprint!|print!)\(", line): + offenders.append(f"{path}:{lineno}") +if offenders: + print(f"print macros outside cmd/ and main.rs reporter: {', '.join(offenders)}") + sys.exit(1) +print("print macros confined to cli cmd/, main.rs reporter, build scripts, and test code") +PY + +# --- env reads at the config edge --------------------------------------------- +check_python "env-read-locations" - <<'PY' +import os, re, sys +from pathlib import Path + +exec(os.environ["CANON_SCAN_HELPER"]) + +# Env reads live in each crate's config module (canon section 7 item 3). +ALLOWED_PREFIXES = ( + "crates/agentfs-core/src/config/", + "crates/agentfs-fuse/src/adapter/config.rs", + "crates/agentfs-cli/src/config.rs", +) + +offenders = [] +for path in sorted(Path("crates").rglob("*.rs")): + parts = path.parts + if "target" in parts or path.name == "tests.rs" or "tests" in parts: + continue + if str(path).startswith(ALLOWED_PREFIXES): + continue + for lineno, line in nontest_lines(path): + if re.search(r"\benv::(var|var_os)\b", line): + offenders.append(f"{path}:{lineno}") +if offenders: + print(f"env reads outside config modules: {', '.join(offenders)}") + sys.exit(1) +print("runtime env reads confined to config modules") +PY + +# --- EnvFilter coverage -------------------------------------------------------- +check_python "envfilter-coverage" - <<'PY' +import sys +from pathlib import Path + +logging = Path("crates/agentfs-cli/src/logging.rs").read_text() +missing = [ + target for target in + ("agentfs=", "agentfs_cli=", "agentfs_core=", "agentfs_fuse=", "agentfs_nfs=", "agentfs_mount=") + if target not in logging +] +if missing: + print(f"DEFAULT_ENV_FILTER missing crate targets: {missing}") + sys.exit(1) +print("DEFAULT_ENV_FILTER names every first-party crate target") +PY + +# --- await_holding_lock lint ----------------------------------------------------- +check_python "await-holding-lock" - <<'PY' +import sys +from pathlib import Path + +root = Path("Cargo.toml").read_text() +if 'await_holding_lock = "deny"' not in root: + print("workspace lints table does not deny clippy::await_holding_lock") + sys.exit(1) +missing = [ + crate for crate in ("agentfs-cli", "agentfs-core", "agentfs-fuse", "agentfs-nfs", "agentfs-mount") + if "workspace = true" not in Path(f"crates/{crate}/Cargo.toml").read_text().split("[lints]")[-1] + or "[lints]" not in Path(f"crates/{crate}/Cargo.toml").read_text() +] +if missing: + print(f"crates missing [lints] workspace = true: {missing}") + sys.exit(1) +print('clippy::await_holding_lock = "deny" via [workspace.lints]; all crates opt in') +PY + +# --- lock-order headers ------------------------------------------------------------- +check_python "lock-order-headers" - <<'PY' +import re, sys +from pathlib import Path + +modules = [ + "crates/agentfs-core/src/fs/agentfs/batcher.rs", + "crates/agentfs-fuse/src/adapter/cache.rs", + "crates/agentfs-core/src/fs/overlay/mod.rs", + "crates/agentfs-core/src/semantics/handles.rs", +] +missing = [ + m for m in modules + if not re.search(r"lock order", Path(m).read_text(), re.I) +] +if missing: + print(f"multi-lock modules missing a lock-order header: {missing}") + sys.exit(1) +print("batcher, adapter cache, overlay, handle table document their lock order") +PY + +# --- docs layout ------------------------------------------------------------------ +check_python "docs-layout" - <<'PY' +import sys +from pathlib import Path + +problems = [] +for doc in ("MANUAL.md", "TESTING.md", "SPEC.md", "KNOBS.md"): + if not Path("docs", doc).is_file(): + problems.append(f"docs/{doc} missing") + if Path(doc).exists(): + problems.append(f"{doc} must live under docs/, found at repo root") +if problems: + print("; ".join(problems)) + sys.exit(1) +print("user docs live under docs/ (MANUAL, TESTING, SPEC, KNOBS)") +PY + +# --- changelog ---------------------------------------------------------------------- +check_python "changelog" - <<'PY' +import sys +from pathlib import Path + +path = Path("CHANGELOG.md") +if not path.is_file() or not path.read_text().strip(): + print("CHANGELOG.md missing or empty at repo root") + sys.exit(1) +print("CHANGELOG.md present at repo root") +PY + +if [ "$FAILURES" -ne 0 ]; then + printf '\nconsistency-canon: %d rule(s) failed\n' "$FAILURES" + exit 1 +fi +printf '\nconsistency-canon: all rules PASS\n' diff --git a/scripts/validation/ddl-census.py b/scripts/validation/ddl-census.py new file mode 100755 index 00000000..e78acf06 --- /dev/null +++ b/scripts/validation/ddl-census.py @@ -0,0 +1,429 @@ +#!/usr/bin/env python3 +"""Test-aware census for VAL-CORE-014 schema DDL centralization.""" + +from __future__ import annotations + +import argparse +import re +import sys +import tempfile +from dataclasses import dataclass +from pathlib import Path + + +DDL_RE = re.compile( + r"CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE" + r"|ALTER\s+TABLE" + r"|PRAGMA\s+user_version" + r"|[\"']schema_version[\"']", + re.IGNORECASE, +) + +SCHEMA_AUTHORITY_FILES = { + Path("sdk/rust/src/schema.rs"), +} +SCHEMA_AUTHORITY_DIRS = ( + Path("sdk/rust/src/schema"), + Path("crates/agentfs-core/src/schema"), +) + +EXCLUDED_DIR_NAMES = {".git", "target"} +EXCLUDED_PREFIXES = ( + Path(".agents/benchmarks/fixtures"), + Path(".agents/kernel"), +) + + +@dataclass(frozen=True) +class Match: + path: Path + line_no: int + text: str + + def render(self) -> str: + return f"{self.path}:{self.line_no}:{self.text.strip()}" + + +@dataclass(frozen=True) +class CensusResult: + inside: list[Match] + outside: list[Match] + path_included_tests: set[Path] + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--root", + type=Path, + default=Path.cwd(), + help="Repository root to scan (default: current working directory)", + ) + parser.add_argument( + "--self-test", + action="store_true", + help="Run ddl-census fixture checks and exit", + ) + return parser.parse_args() + + +def rel_to(root: Path, path: Path) -> Path: + return path.relative_to(root).as_posix() + + +def is_under(path: Path, parent: Path) -> bool: + try: + path.relative_to(parent) + return True + except ValueError: + return False + + +def is_excluded(root: Path, path: Path) -> bool: + rel = path.relative_to(root) + if any(part in EXCLUDED_DIR_NAMES for part in rel.parts): + return True + if rel.parts and rel.parts[0] == ".agents" and len(rel.parts) > 1: + if re.fullmatch(r"\d{2}_\d{2}_\d{4}", rel.parts[1]): + return True + return any(is_under(rel, prefix) for prefix in EXCLUDED_PREFIXES) + + +def rust_files(root: Path) -> list[Path]: + return sorted( + path + for path in root.rglob("*.rs") + if path.is_file() and not is_excluded(root, path) + ) + + +def cfg_expr(attr: str) -> str | None: + match = re.fullmatch(r"#\s*\[\s*cfg\s*\((.*)\)\s*\]\s*", attr) + if match: + return match.group(1).strip() + return None + + +def split_top_level_args(text: str) -> list[str]: + args: list[str] = [] + depth = 0 + start = 0 + in_string: str | None = None + escaped = False + for idx, ch in enumerate(text): + if in_string: + if escaped: + escaped = False + elif ch == "\\": + escaped = True + elif ch == in_string: + in_string = None + continue + if ch in ("'", '"'): + in_string = ch + continue + if ch == "(": + depth += 1 + continue + if ch == ")": + depth -= 1 + continue + if ch == "," and depth == 0: + args.append(text[start:idx].strip()) + start = idx + 1 + tail = text[start:].strip() + if tail: + args.append(tail) + return args + + +def cfg_is_test_only(expr: str) -> bool: + if expr == "test": + return True + match = re.fullmatch(r"all\s*\((.*)\)", expr, flags=re.DOTALL) + if not match: + return False + return any(arg == "test" for arg in split_top_level_args(match.group(1))) + + +def has_test_cfg(attrs: list[str]) -> bool: + return any( + cfg_is_test_only(expr) + for attr in attrs + if (expr := cfg_expr(attr)) is not None + ) + + +def path_attr(attrs: list[str]) -> str | None: + for attr in attrs: + match = re.search(r'#\s*\[\s*path\s*=\s*"([^"]+)"\s*\]', attr) + if match: + return match.group(1) + return None + + +def line_offsets(text: str) -> list[int]: + offsets = [] + offset = 0 + for line in text.splitlines(keepends=True): + offsets.append(offset) + offset += len(line) + return offsets + + +def line_for_offset(offsets: list[int], offset: int) -> int: + lo = 0 + hi = len(offsets) + while lo + 1 < hi: + mid = (lo + hi) // 2 + if offsets[mid] <= offset: + lo = mid + else: + hi = mid + return lo + + +def matching_brace(text: str, open_index: int) -> int | None: + depth = 0 + in_string: str | None = None + escaped = False + in_line_comment = False + in_block_comment = False + i = open_index + while i < len(text): + ch = text[i] + nxt = text[i + 1] if i + 1 < len(text) else "" + + if in_line_comment: + if ch == "\n": + in_line_comment = False + i += 1 + continue + if in_block_comment: + if ch == "*" and nxt == "/": + in_block_comment = False + i += 2 + else: + i += 1 + continue + if in_string: + if escaped: + escaped = False + elif ch == "\\": + escaped = True + elif ch == in_string: + in_string = None + i += 1 + continue + + if ch == "/" and nxt == "/": + in_line_comment = True + i += 2 + continue + if ch == "/" and nxt == "*": + in_block_comment = True + i += 2 + continue + if ch in ("'", '"'): + in_string = ch + i += 1 + continue + if ch == "{": + depth += 1 + elif ch == "}": + depth -= 1 + if depth == 0: + return i + i += 1 + return None + + +def item_span_from_attrs(text: str, offsets: list[int], item_line: int) -> tuple[int, int] | None: + start = offsets[item_line] + next_line = offsets[item_line + 1] if item_line + 1 < len(offsets) else len(text) + semicolon = text.find(";", start, next_line) + open_brace = text.find("{", start) + if open_brace == -1: + return None + if semicolon != -1 and semicolon < open_brace: + return None + close = matching_brace(text, open_brace) + if close is None: + return None + return (item_line + 1, line_for_offset(offsets, close) + 1) + + +def test_ranges_and_path_includes(root: Path, path: Path, text: str) -> tuple[set[int], set[Path]]: + offsets = line_offsets(text) + lines = text.splitlines() + test_lines: set[int] = set() + path_includes: set[Path] = set() + attrs: list[str] = [] + + for idx, line in enumerate(lines): + stripped = line.strip() + if stripped.startswith("#["): + attrs.append(stripped) + continue + + if attrs and (not stripped or stripped.startswith("//")): + continue + + if not attrs: + continue + + if has_test_cfg(attrs): + include = path_attr(attrs) + if include and re.search(r"\bmod\b", stripped) and stripped.endswith(";"): + path_includes.add((path.parent / include).resolve()) + span = item_span_from_attrs(text, offsets, idx) + if span: + start, end = span + test_lines.update(range(start, end + 1)) + attrs = [] + + return test_lines, path_includes + + +def schema_authority(root: Path, path: Path) -> bool: + rel = path.relative_to(root) + if rel in SCHEMA_AUTHORITY_FILES: + return True + return any(is_under(rel, authority_dir) for authority_dir in SCHEMA_AUTHORITY_DIRS) + + +def test_file(root: Path, path: Path, path_included_tests: set[Path]) -> bool: + rel = path.relative_to(root) + if path.resolve() in path_included_tests: + return True + if path.name == "tests.rs": + return True + return "tests" in rel.parts + + +def find_matches(root: Path, path: Path, text: str, skip_lines: set[int]) -> list[Match]: + rel = Path(rel_to(root, path)) + matches: list[Match] = [] + for idx, line in enumerate(text.splitlines(), start=1): + if idx in skip_lines: + continue + if DDL_RE.search(line): + matches.append(Match(rel, idx, line)) + return matches + + +def run_census(root: Path) -> CensusResult: + root = root.resolve() + files = rust_files(root) + + texts: dict[Path, str] = {} + test_ranges: dict[Path, set[int]] = {} + path_included_tests: set[Path] = set() + for path in files: + text = path.read_text(encoding="utf-8") + texts[path] = text + ranges, includes = test_ranges_and_path_includes(root, path, text) + test_ranges[path] = ranges + path_included_tests.update(includes) + + inside: list[Match] = [] + outside: list[Match] = [] + for path, text in texts.items(): + if schema_authority(root, path): + inside.extend(find_matches(root, path, text, set())) + continue + if test_file(root, path, path_included_tests): + continue + outside.extend(find_matches(root, path, text, test_ranges[path])) + + return CensusResult(inside, outside, path_included_tests) + + +def emit_result(result: CensusResult) -> int: + print(f"inside_schema_matches={len(result.inside)}") + print(f"outside_schema_matches={len(result.outside)}") + print(f"path_included_test_files={len(result.path_included_tests)}") + + if result.inside: + print("schema_authority_positive_sample:") + for match in result.inside[:10]: + print(f" {match.render()}") + + if result.outside: + print("outside_schema_match_details:") + for match in result.outside: + print(f" {match.render()}") + return 1 + + if not result.inside: + print("error: no schema authority DDL matches found", file=sys.stderr) + return 1 + + return 0 + + +def run_self_test() -> int: + with tempfile.TemporaryDirectory(prefix="ddl-census-fixture-") as tmp: + root = Path(tmp) + schema_dir = root / "crates/agentfs-core/src/schema" + schema_dir.mkdir(parents=True) + (schema_dir / "mod.rs").write_text( + 'pub const AUTHORITY_DDL: &str = "CREATE TABLE authority (id INTEGER)";\n', + encoding="utf-8", + ) + + src_dir = root / "crates/agentfs-core/src" + (src_dir / "lib.rs").write_text( + r''' +#[cfg(test)] +mod inline_tests { + const TEST_DDL: &str = "CREATE TABLE ignored_inline_test (id INTEGER)"; +} + +#[cfg(all(test, feature = "ddl-tests"))] +mod all_test_feature_tests { + const TEST_DDL: &str = "ALTER TABLE ignored_all_test ADD COLUMN value INTEGER"; +} + +#[cfg(test)] +#[path = "ddl_tests.rs"] +mod ddl_tests; + +#[cfg(feature = "test-utils")] +pub fn production_feature_named_test_utils() { + let _sql = "CREATE TABLE production_feature_named_test_utils (id INTEGER)"; +} +'''.lstrip(), + encoding="utf-8", + ) + (src_dir / "ddl_tests.rs").write_text( + 'const PATH_TEST_DDL: &str = "CREATE TABLE ignored_path_test (id INTEGER)";\n', + encoding="utf-8", + ) + + result = run_census(root) + outside = "\n".join(match.render() for match in result.outside) + assert result.inside, "fixture should include schema-authority DDL" + assert any( + "production_feature_named_test_utils" in match.text for match in result.outside + ), f"cfg(feature = \"test-utils\") DDL must remain production-visible:\n{outside}" + assert "ignored_inline_test" not in outside + assert "ignored_all_test" not in outside + assert "ignored_path_test" not in outside + assert ( + len(result.path_included_tests) == 1 + ), f"expected one cfg(test) #[path] include, got {result.path_included_tests}" + + print("ddl-census self-test passed") + return 0 + + +def main() -> int: + args = parse_args() + if args.self_test: + return run_self_test() + return emit_result(run_census(args.root)) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/validation/external-base-mutation-coherence.py b/scripts/validation/external-base-mutation-coherence.py new file mode 100644 index 00000000..30679cdd --- /dev/null +++ b/scripts/validation/external-base-mutation-coherence.py @@ -0,0 +1,577 @@ +#!/usr/bin/env python3 +"""Validate that overlay base-file drift never serves stale mounted bytes.""" + +from __future__ import annotations + +import argparse +import contextlib +import hashlib +import json +import os +import re +import signal +import shutil +import subprocess +import sys +import tempfile +import time +from pathlib import Path +from typing import Any + + +OUTPUT_TAIL_CHARS = 8000 +DEFAULT_CLEANUP_TIMEOUT = 10.0 +FINAL_UNMOUNT_SETTLE_TIMEOUT = 0.5 +STALE_BYTES = b"before-base-content\n" +FRESH_BYTES = b"after-base-content-with-new-size\n" +MOUNTINFO_ESCAPE_RE = re.compile(r"\\([0-7]{3})") +EIO_MARKERS = ("Input/output error", "EIO") + + +def tail_text(text: str) -> str: + return text if len(text) <= OUTPUT_TAIL_CHARS else text[-OUTPUT_TAIL_CHARS:] + + +def resolve_agentfs_bin(agentfs_bin: str | None, repo_root: Path) -> str: + if agentfs_bin: + candidate = Path(agentfs_bin).expanduser() + if candidate.is_file() and os.access(candidate, os.X_OK): + return str(candidate.resolve()) + if os.sep not in agentfs_bin: + found = shutil.which(agentfs_bin) + if found: + return found + raise RuntimeError(f"agentfs binary not found or not executable: {agentfs_bin}") + + candidate = repo_root / "target" / "release" / "agentfs" + if candidate.is_file() and os.access(candidate, os.X_OK): + return str(candidate) + raise RuntimeError("no release agentfs binary found, pass --agentfs-bin or build release") + + +def sha256(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def parse_profile_summary(output: str) -> dict[str, Any]: + for line in reversed(output.splitlines()): + if '"agentfs_profile_summary"' not in line: + continue + start = line.find("{") + if start < 0: + continue + try: + value = json.loads(line[start:]) + except json.JSONDecodeError: + continue + counters = value.get("counters") + if isinstance(counters, dict): + return counters + return {} + + +def run_checked(argv: list[str], cwd: Path, env: dict[str, str], timeout: float) -> subprocess.CompletedProcess[str]: + return subprocess.run( + argv, + cwd=str(cwd), + env=env, + text=True, + capture_output=True, + timeout=timeout, + ) + + +def is_eio_failure(proc: subprocess.CompletedProcess[Any]) -> bool: + if proc.returncode == 0: + return False + stderr = proc.stderr + if isinstance(stderr, bytes): + stderr_text = stderr.decode("utf-8", "replace") + else: + stderr_text = stderr or "" + return any(marker in stderr_text for marker in EIO_MARKERS) + + +def decode_mountinfo_field(value: str) -> str: + return MOUNTINFO_ESCAPE_RE.sub(lambda match: chr(int(match.group(1), 8)), value) + + +def mountinfo_entry(mountpoint: Path) -> dict[str, str] | None: + target = str(mountpoint) + try: + lines = Path("/proc/self/mountinfo").read_text().splitlines() + except OSError: + return None + + for line in lines: + parts = line.split() + if len(parts) < 10: + continue + try: + separator = parts.index("-") + except ValueError: + continue + mounted_at = decode_mountinfo_field(parts[4]) + if mounted_at != target: + continue + return { + "mountpoint": mounted_at, + "device": parts[2], + "fstype": parts[separator + 1] if separator + 1 < len(parts) else "", + "source": parts[separator + 2] if separator + 2 < len(parts) else "", + "line": line, + } + return None + + +def is_mounted(mountpoint: Path) -> bool: + return mountinfo_entry(mountpoint) is not None + + +def wait_for_mount(mountpoint: Path, timeout: float) -> bool: + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + if is_mounted(mountpoint): + return True + time.sleep(0.1) + return False + + +def wait_for_unmount(mountpoint: Path, timeout: float) -> bool: + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + if not is_mounted(mountpoint): + return True + time.sleep(0.1) + return not is_mounted(mountpoint) + + +def fuse_connection_id_for_mount(mountpoint: Path) -> str | None: + entry = mountinfo_entry(mountpoint) + if entry is None or not entry["fstype"].startswith("fuse"): + return None + major_minor = entry["device"].split(":", 1) + if len(major_minor) != 2 or major_minor[0] != "0": + return None + return major_minor[1] + + +def abort_fuse_connection(mountpoint: Path) -> dict[str, Any]: + connection_id = fuse_connection_id_for_mount(mountpoint) + report: dict[str, Any] = { + "attempted": connection_id is not None, + "connection_id": connection_id, + "ok": False, + } + if connection_id is None: + report["reason"] = "no-fuse-connection-for-mountpoint" + return report + + abort_path = Path("/sys/fs/fuse/connections") / connection_id / "abort" + try: + abort_path.write_text("1\n") + except FileNotFoundError: + report["reason"] = "connection-already-gone" + report["ok"] = True + except OSError as exc: + report["reason"] = f"{type(exc).__name__}: {exc}" + else: + report["ok"] = True + report["reason"] = "aborted" + return report + + +def run_cleanup_command(command: list[str], timeout: float, mountpoint: Path | None = None) -> dict[str, Any]: + report: dict[str, Any] = {"command": command, "timed_out": False} + try: + proc = subprocess.Popen(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except FileNotFoundError: + report.update({"returncode": None, "missing": True}) + return report + + report["pid"] = proc.pid + try: + report["returncode"] = proc.wait(timeout=timeout) + return report + except subprocess.TimeoutExpired: + report["timed_out"] = True + if mountpoint is not None: + report["abort_fallback"] = abort_fuse_connection(mountpoint) + for action in ("terminate", "kill"): + if proc.poll() is not None: + break + try: + getattr(proc, action)() + except ProcessLookupError: + break + try: + proc.wait(timeout=1.0) + except subprocess.TimeoutExpired: + continue + report["returncode"] = proc.poll() + report["alive_after_kill"] = proc.poll() is None + return report + + +def unmount(mountpoint: Path, cleanup_timeout: float = DEFAULT_CLEANUP_TIMEOUT) -> dict[str, Any]: + report: dict[str, Any] = { + "mountpoint": str(mountpoint), + "mounted_before": is_mounted(mountpoint), + "connection_id_before": fuse_connection_id_for_mount(mountpoint), + "commands": [], + "abort_fallbacks": [], + } + if not report["mounted_before"]: + report["mounted_after"] = False + return report + + for command in (["fusermount3", "-u", str(mountpoint)], ["fusermount", "-u", str(mountpoint)]): + command_report = run_cleanup_command(command, cleanup_timeout, mountpoint) + report["commands"].append(command_report) + if wait_for_unmount(mountpoint, 1.0): + report["mounted_after"] = False + return report + + abort_report = abort_fuse_connection(mountpoint) + report["abort_fallbacks"].append(abort_report) + if abort_report.get("ok"): + wait_for_unmount(mountpoint, cleanup_timeout) + if is_mounted(mountpoint): + retry = run_cleanup_command(["fusermount3", "-u", str(mountpoint)], cleanup_timeout, mountpoint) + report["commands"].append(retry) + + wait_for_unmount(mountpoint, FINAL_UNMOUNT_SETTLE_TIMEOUT) + report["mounted_after"] = is_mounted(mountpoint) + return report + + +def terminate_mount_process( + proc: subprocess.Popen[str] | None, + mountpoint: Path, + cleanup_timeout: float = DEFAULT_CLEANUP_TIMEOUT, +) -> dict[str, Any]: + report: dict[str, Any] = {"started": proc is not None} + if proc is None: + return report + report["pid"] = proc.pid + if proc.poll() is not None: + report["returncode"] = proc.returncode + return report + + try: + proc.terminate() + except ProcessLookupError: + report["returncode"] = proc.poll() + return report + try: + report["returncode"] = proc.wait(timeout=cleanup_timeout) + report["terminated"] = True + return report + except subprocess.TimeoutExpired: + report["terminated"] = False + report["abort_fallback"] = abort_fuse_connection(mountpoint) + + if proc.poll() is None: + try: + proc.kill() + except ProcessLookupError: + pass + try: + proc.wait(timeout=1.0) + except subprocess.TimeoutExpired: + pass + report["returncode"] = proc.poll() + report["alive_after_kill"] = proc.poll() is None + return report + + +@contextlib.contextmanager +def defer_cleanup_interrupts() -> Any: + deferred: list[int] = [] + previous_handlers: dict[int, Any] = {} + + def record_signal(signum: int, _frame: Any) -> None: + deferred.append(signum) + + for signum in (signal.SIGINT, signal.SIGTERM): + previous_handlers[signum] = signal.getsignal(signum) + signal.signal(signum, record_signal) + try: + yield deferred + finally: + for signum, handler in previous_handlers.items(): + signal.signal(signum, handler) + + +def cleanup_mount( + mountpoint: Path, + proc: subprocess.Popen[str] | None, + cleanup_timeout: float = DEFAULT_CLEANUP_TIMEOUT, +) -> dict[str, Any]: + with defer_cleanup_interrupts() as deferred_signals: + report = { + "unmount": unmount(mountpoint, cleanup_timeout), + "process": terminate_mount_process(proc, mountpoint, cleanup_timeout), + "mounted_after_cleanup": is_mounted(mountpoint), + } + if deferred_signals: + report["deferred_signals"] = deferred_signals + return report + + +def raise_keyboard_interrupt(signum: int, _frame: Any) -> None: + raise KeyboardInterrupt(f"signal {signum}") + + +def run_leg( + label: str, + extra_env: dict[str, str], + expect_noopen: bool, + agentfs_bin: str, + timeout: float, + cleanup_timeout: float, +) -> dict[str, Any]: + started = time.perf_counter() + with tempfile.TemporaryDirectory(prefix=f"agentfs-base-drift-{label}-") as tmp: + root = Path(tmp) + work = root / "work" + base = root / "base" + mountpoint = root / "mnt" + work.mkdir() + base.mkdir() + mountpoint.mkdir() + base_file = base / "base.txt" + base_file.write_bytes(STALE_BYTES) + + env = os.environ.copy() + env.update(extra_env) + env["AGENTFS_PROFILE"] = "1" + + init = run_checked([agentfs_bin, "init", "drift", "--base", str(base)], work, env, timeout) + if init.returncode != 0: + return { + "label": label, + "passed": False, + "phase": "init", + "returncode": init.returncode, + "stderr_tail": tail_text(init.stderr), + } + + mount_log = root / "mount.log" + mount_output = "" + mount_out = mount_log.open("w", encoding="utf-8") + proc: subprocess.Popen[str] | None = None + result: dict[str, Any] | None = None + try: + proc = subprocess.Popen( + [ + agentfs_bin, + "mount", + str(work / ".agentfs" / "drift.db"), + str(mountpoint), + "--foreground", + ], + cwd=str(work), + env=env, + stdout=mount_out, + stderr=subprocess.STDOUT, + text=True, + ) + if not wait_for_mount(mountpoint, min(timeout, 20.0)): + result = { + "label": label, + "passed": False, + "phase": "mount", + "returncode": proc.poll(), + } + return result + + first = subprocess.run( + ["cat", str(mountpoint / "base.txt")], + text=False, + capture_output=True, + timeout=timeout, + ) + if first.returncode != 0 or first.stdout != STALE_BYTES: + result = { + "label": label, + "passed": False, + "phase": "prime_read", + "returncode": first.returncode, + "stdout": first.stdout.decode("utf-8", "replace"), + "stderr_tail": tail_text(first.stderr.decode("utf-8", "replace")), + } + return result + + before_stat = base_file.stat() + before_hash = sha256(base_file.read_bytes()) + + # Keep mtime distinguishable on filesystems with coarse timestamps. + time.sleep(1.1) + base_file.write_bytes(FRESH_BYTES) + after_stat = base_file.stat() + after_hash = sha256(base_file.read_bytes()) + + stat_after = subprocess.run( + ["stat", "-c", "%s %Y", str(mountpoint / "base.txt")], + text=True, + capture_output=True, + timeout=timeout, + ) + if stat_after.returncode == 0: + time.sleep(0.2) + read_after = subprocess.run( + ["cat", str(mountpoint / "base.txt")], + text=False, + capture_output=True, + timeout=timeout, + ) + else: + read_after = subprocess.CompletedProcess( + args=["cat", str(mountpoint / "base.txt")], + returncode=stat_after.returncode, + stdout=b"", + stderr=stat_after.stderr.encode("utf-8", "replace"), + ) + except KeyboardInterrupt as exc: + result = { + "label": label, + "passed": False, + "phase": "interrupted", + "interrupted": True, + "duration_seconds": time.perf_counter() - started, + "interrupt": str(exc) or "keyboard interrupt", + } + return result + finally: + mount_out.close() + cleanup = cleanup_mount(mountpoint, proc, cleanup_timeout) + if mount_log.exists(): + mount_output = mount_log.read_text(encoding="utf-8", errors="replace") + if result is not None: + result["cleanup"] = cleanup + result.setdefault("mount_output_tail", tail_text(mount_output)) + + counters = parse_profile_summary(mount_output) + stale = read_after.returncode == 0 and read_after.stdout == STALE_BYTES + fresh = read_after.returncode == 0 and read_after.stdout == FRESH_BYTES + drift_error = is_eio_failure(read_after) or is_eio_failure(stat_after) + stat_size = stat_after.stdout.strip().split(" ")[0] if stat_after.returncode == 0 else None + stat_fresh = stat_size == str(len(FRESH_BYTES)) + noopen_ok = True + if expect_noopen: + noopen_ok = counters.get("fuse_noopen_enosys_replies") == 1 and counters.get("fuse_op_open_count") == 1 + else: + noopen_ok = (counters.get("fuse_op_open_count") or 0) >= 1 and ( + counters.get("fuse_noopen_enosys_replies") or 0 + ) == 0 + invalidations = counters.get("base_fast_inode_invalidations") or 0 + stale_rejections = counters.get("base_fast_stale_rejections") or 0 + + return { + "label": label, + "passed": (fresh and stat_fresh and not stale and noopen_ok) + or (drift_error and not stale and noopen_ok), + "duration_seconds": time.perf_counter() - started, + "before_hash": before_hash, + "after_hash": after_hash, + "before_size": before_stat.st_size, + "after_size": after_stat.st_size, + "before_mtime_ns": before_stat.st_mtime_ns, + "after_mtime_ns": after_stat.st_mtime_ns, + "mounted_read_returncode": read_after.returncode, + "mounted_read": read_after.stdout.decode("utf-8", "replace"), + "mounted_read_stderr_tail": tail_text(read_after.stderr.decode("utf-8", "replace")), + "mounted_stat_returncode": stat_after.returncode, + "mounted_stat": stat_after.stdout.strip(), + "stale": stale, + "fresh": fresh, + "drift_error": drift_error, + "drift_errno": "EIO" if drift_error else None, + "stat_fresh": stat_fresh, + "noopen_ok": noopen_ok, + "fuse_op_open_count": counters.get("fuse_op_open_count"), + "fuse_noopen_enosys_replies": counters.get("fuse_noopen_enosys_replies"), + "base_fast_inode_invalidations": invalidations, + "base_fast_stale_rejections": stale_rejections, + "profile_counters_present": bool(counters), + "mount_output_tail": tail_text(mount_output), + "cleanup": cleanup, + } + + +def main() -> int: + signal.signal(signal.SIGINT, raise_keyboard_interrupt) + signal.signal(signal.SIGTERM, raise_keyboard_interrupt) + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--agentfs-bin", default=os.environ.get("AGENTFS_BIN")) + parser.add_argument("--timeout", type=float, default=60.0) + parser.add_argument("--cleanup-timeout", type=float, default=DEFAULT_CLEANUP_TIMEOUT) + parser.add_argument("--output", default=None) + args = parser.parse_args() + + repo_root = Path(__file__).resolve().parents[2] + agentfs_bin = resolve_agentfs_bin(args.agentfs_bin, repo_root) + legs = [ + ("default_noopen", {}, True), + ("noopen_off", {"AGENTFS_FUSE_NOOPEN": "0"}, False), + ] + runs = [] + interrupted = False + for label, env, noopen in legs: + leg_started = time.perf_counter() + try: + run = run_leg(label, env, noopen, agentfs_bin, args.timeout, args.cleanup_timeout) + except KeyboardInterrupt as exc: + run = { + "label": label, + "passed": False, + "phase": "interrupted", + "interrupted": True, + "duration_seconds": time.perf_counter() - leg_started, + "interrupt": str(exc) or "keyboard interrupt", + } + runs.append(run) + if run.get("interrupted"): + interrupted = True + break + report = { + "schema_version": 1, + "agentfs_bin": agentfs_bin, + "passed": not interrupted and all(run.get("passed") for run in runs), + "interrupted": interrupted, + "runs": runs, + } + output = args.output or os.path.join( + tempfile.gettempdir(), + f"agentfs-external-base-mutation-{time.strftime('%Y%m%d-%H%M%S')}.json", + ) + Path(output).write_text(json.dumps(report, indent=2)) + + for run in runs: + if run.get("interrupted") or run.get("phase") == "interrupted" or run.get("run_phase") == "interrupted": + print( + f"INTERRUPTED {run['label']:15s} " + f"duration={run.get('duration_seconds')} interrupt={run.get('interrupt')!r}" + ) + continue + status = "PASS" if run.get("passed") else "FAIL" + print( + f"{status} {run['label']:15s} stale={run.get('stale')} " + f"fresh={run.get('fresh')} stat_fresh={run.get('stat_fresh')} " + f"read_rc={run.get('mounted_read_returncode')} opens={run.get('fuse_op_open_count')} " + f"enosys={run.get('fuse_noopen_enosys_replies')} " + f"base_invalidations={run.get('base_fast_inode_invalidations')} " + f"stale_rejections={run.get('base_fast_stale_rejections')}" + ) + if not run.get("passed"): + print(f" mounted_read={run.get('mounted_read')!r}") + print(f" mounted_stat={run.get('mounted_stat')!r}") + print(f"report: {output}") + if interrupted: + return 130 + return 0 if report["passed"] else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/validation/flush-coherence.py b/scripts/validation/flush-coherence.py index de82b2ef..e074e387 100644 --- a/scripts/validation/flush-coherence.py +++ b/scripts/validation/flush-coherence.py @@ -29,7 +29,6 @@ import argparse import json import os -import shutil import subprocess import sys import tempfile @@ -37,7 +36,8 @@ from pathlib import Path from typing import Any, Optional -OUTPUT_TAIL_CHARS = 8000 +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from lib.common import resolve_agentfs_bin, tail_text # noqa: E402 WORKLOAD = r''' import ctypes @@ -112,29 +112,6 @@ def write_close(path, size): ''' -def tail_text(text: str) -> str: - return text if len(text) <= OUTPUT_TAIL_CHARS else text[-OUTPUT_TAIL_CHARS:] - - -def resolve_agentfs_bin(agentfs_bin: Optional[str], repo_root: Path) -> str: - if agentfs_bin: - candidate = Path(agentfs_bin).expanduser() - if candidate.is_file() and os.access(candidate, os.X_OK): - return str(candidate.resolve()) - if os.sep not in agentfs_bin: - found = shutil.which(agentfs_bin) - if found: - return found - raise RuntimeError(f"agentfs binary not found or not executable: {agentfs_bin}") - for candidate in ( - repo_root / "cli" / "target" / "release" / "agentfs", - repo_root / "cli" / "target" / "debug" / "agentfs", - ): - if candidate.is_file() and os.access(candidate, os.X_OK): - return str(candidate) - raise RuntimeError("no agentfs binary found; pass --agentfs-bin or set AGENTFS_BIN") - - def parse_workload_json(stdout: str) -> Optional[dict[str, Any]]: for line in reversed(stdout.splitlines()): line = line.strip() @@ -151,7 +128,7 @@ def parse_workload_json(stdout: str) -> Optional[dict[str, Any]]: def parse_fuse_counters(output: str) -> Optional[dict[str, Any]]: for line in reversed(output.splitlines()): - if '"agentfs_profile_summary"' not in line or '"fuse_session"' not in line: + if '"agentfs_profile_summary"' not in line: continue start = line.find("{") if start < 0: @@ -161,7 +138,7 @@ def parse_fuse_counters(output: str) -> Optional[dict[str, Any]]: except json.JSONDecodeError: continue counters = value.get("counters") - if isinstance(counters, dict): + if isinstance(counters, dict) and any(key.startswith("fuse_") for key in counters): return counters return None diff --git a/scripts/validation/git-workload-benchmark-multi.py b/scripts/validation/git-workload-benchmark-multi.py index a6a98f02..2e2ca034 100755 --- a/scripts/validation/git-workload-benchmark-multi.py +++ b/scripts/validation/git-workload-benchmark-multi.py @@ -24,6 +24,9 @@ from pathlib import Path from typing import Any +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from lib import common # noqa: E402 + def percentile(values: list[float], q: float) -> float: if not values: @@ -226,6 +229,9 @@ def main(argv: list[str]) -> int: with tempfile.TemporaryDirectory(prefix="gw-bench-multi-") as tmpdir: tmp_root = Path(tmpdir) + git_ai_before = common.git_ai_processes() + common.pin_distro_git(os.environ, tmp_root) + warmup_runs: list[dict[str, Any]] = [] for i in range(args.warmup): out_path = (persist_dir / f"warmup-{i:02d}.json") if persist_dir else (tmp_root / f"warmup-{i:02d}.json") @@ -248,6 +254,8 @@ def main(argv: list[str]) -> int: flush=True, ) + leaked_git_ai = common.git_ai_leaks(git_ai_before, common.git_ai_processes()) + runs_for_aggregation = [r.get("result") or {} for r in runs] aggregation = aggregate(runs_for_aggregation) aggregation["label"] = args.label @@ -256,6 +264,10 @@ def main(argv: list[str]) -> int: aggregation["agentfs_bin"] = args.agentfs_bin aggregation["iteration_returncodes"] = [r["returncode"] for r in runs] aggregation["iteration_wall_seconds"] = [r["wall_seconds"] for r in runs] + aggregation["git_ai_census"] = { + "pre_existing": len(git_ai_before), + "leaked": leaked_git_ai, + } human = render_human(args.label, aggregation) print(human, file=sys.stderr, flush=True) @@ -266,6 +278,14 @@ def main(argv: list[str]) -> int: else: sys.stdout.write(payload) + if leaked_git_ai: + print( + f"ERROR: benchmark run leaked {len(leaked_git_ai)} git-ai process(es); " + "the pinned-git shim must cover every git invocation", + file=sys.stderr, + ) + return 1 + return 0 diff --git a/scripts/validation/git-workload-benchmark.py b/scripts/validation/git-workload-benchmark.py index 3a6c91d4..5e4b46c2 100755 --- a/scripts/validation/git-workload-benchmark.py +++ b/scripts/validation/git-workload-benchmark.py @@ -19,6 +19,9 @@ from statistics import mean from typing import Any, Optional +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from lib import common # noqa: E402 + OUTPUT_TAIL_CHARS = 20000 HASH_BLOCK_BYTES = 1024 * 1024 @@ -91,9 +94,13 @@ def git_env(): def run_git(argv, cwd): + # Honor the harness's pinned-git override: PATH shims are invisible inside + # the agentfs sandbox, so only an absolute system-path GIT avoids the + # daemonizing hook-manager shim (scripts/validation/lib/common.py). + git = os.environ.get("GIT", "git") started = time.perf_counter() proc = subprocess.run( - ["git"] + argv, + [git] + argv, cwd=str(cwd), env=git_env(), text=True, @@ -101,7 +108,7 @@ def run_git(argv, cwd): stderr=subprocess.PIPE, ) return { - "argv": ["git"] + argv, + "argv": [git] + argv, "cwd": str(cwd), "duration_seconds": time.perf_counter() - started, "returncode": proc.returncode, @@ -588,6 +595,11 @@ def run_subprocess(argv: list[str], cwd: Path, env: dict[str, str], timeout: flo "duration_seconds": time.perf_counter() - started, "returncode": proc.returncode, "timed_out": timed_out, + # Full stdout is kept for JSON extraction and stripped before the + # record is embedded in the report: the workload's single-line JSON + # can exceed OUTPUT_TAIL_CHARS once temp paths grow (phase8 nests its + # TMPDIR several levels deep), and a truncated tail is unparseable. + "stdout": stdout, "stdout_tail": tail_text(stdout), "stderr_tail": tail_text(stderr), "stdout_bytes": len((stdout or "").encode("utf-8", errors="replace")), @@ -596,8 +608,12 @@ def run_subprocess(argv: list[str], cwd: Path, env: dict[str, str], timeout: flo } +def strip_stdout(run: dict[str, Any]) -> dict[str, Any]: + return {key: value for key, value in run.items() if key != "stdout"} + + def parse_json_stdout(run: dict[str, Any]) -> Optional[dict[str, Any]]: - text = str(run.get("stdout_tail", "")).strip() + text = str(run.get("stdout") or run.get("stdout_tail", "")).strip() if text: try: value = json.loads(text) @@ -633,9 +649,9 @@ def resolve_agentfs_bin(agentfs_bin: Optional[str], repo_root: Path) -> str: # measuring (debug is unoptimized and can be 10x slower), AND release tends # to be rebuilt more often than debug during active development, so we are # more likely to pick up recent source changes. Debug-first ordering bit us - # in Tier One (see RCA in the notes file): a stale debug binary missing the - # `fuse-modern` feature kept returning ENOSYS while the just-built release - # binary worked fine. + # in Tier One (see RCA in the notes file): a stale debug binary from the + # pre-ABI-collapse workspace kept returning ENOSYS while the just-built + # release binary worked fine. for candidate_path in ( repo_root / "cli" / "target" / "release" / "agentfs", repo_root / "cli" / "target" / "debug" / "agentfs", @@ -665,7 +681,7 @@ def resolve_agentfs_bin(agentfs_bin: Optional[str], repo_root: Path) -> str: def git_commit(repo_root: Path) -> Optional[str]: proc = subprocess.run( - ["git", "rev-parse", "HEAD"], + [os.environ.get("GIT", "git"), "rev-parse", "HEAD"], cwd=str(repo_root), text=True, stdout=subprocess.PIPE, @@ -690,10 +706,11 @@ def git_env() -> dict[str, str]: def run_git(argv: list[str], cwd: Path, *, env: Optional[dict[str, str]] = None, timeout: float = 60) -> subprocess.CompletedProcess[str]: + resolved_env = env or git_env() return subprocess.run( - ["git"] + argv, + [resolved_env.get("GIT", "git")] + argv, cwd=str(cwd), - env=env or git_env(), + env=resolved_env, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -702,7 +719,7 @@ def run_git(argv: list[str], cwd: Path, *, env: Optional[dict[str, str]] = None, def require_git() -> None: - if shutil.which("git") is None: + if shutil.which(os.environ.get("GIT", "git")) is None: raise RuntimeError("git executable is required") @@ -843,6 +860,7 @@ def prepare_environment(temp_root: Path, profile: bool) -> dict[str, str]: env["XDG_CONFIG_HOME"] = str(home / ".config") env["XDG_CACHE_HOME"] = str(home / ".cache") env["XDG_DATA_HOME"] = str(home / ".local" / "share") + common.pin_distro_git(env, home, home=home) temp_dir = temp_root / "tmp" temp_dir.mkdir(parents=True, exist_ok=True) @@ -1134,6 +1152,7 @@ def main(argv: list[str]) -> int: exit_code = 0 result: dict[str, Any] try: + common.pin_distro_git(os.environ, temp_root) require_git() agentfs_bin = resolve_agentfs_bin(args.agentfs_bin, repo_root) env = prepare_environment(temp_root, args.profile) @@ -1311,11 +1330,11 @@ def main(argv: list[str]) -> int: "correctness_passed": correctness["passed"], }, "native": { - "run": native_run, + "run": strip_stdout(native_run), "workload": native_workload, }, "agentfs_overlay": { - "run": agentfs_run, + "run": strip_stdout(agentfs_run), "workload": agentfs_workload, "profile_summaries": profile_summaries, }, @@ -1329,12 +1348,12 @@ def main(argv: list[str]) -> int: "inspect_after": inspect_after, "nonempty_sidecars": not no_sidecars, "integrity": { - "run": integrity_run, + "run": strip_stdout(integrity_run), "result": integrity_payload, }, "backup": { "path": str(backup_path), - "run": backup_run, + "run": strip_stdout(backup_run), "inspect": backup_inspect, "artifacts": backup_artifacts, }, diff --git a/scripts/validation/large-edit-benchmark.py b/scripts/validation/large-edit-benchmark.py index 2eaf51b7..f04765b8 100755 --- a/scripts/validation/large-edit-benchmark.py +++ b/scripts/validation/large-edit-benchmark.py @@ -21,7 +21,6 @@ OUTPUT_TAIL_CHARS = 4000 ONE_MIB = 1024 * 1024 -PARTIAL_ORIGIN_ENV = "AGENTFS_OVERLAY_PARTIAL_ORIGIN" EDIT_WORKLOAD = r''' @@ -123,8 +122,6 @@ def parse_args(argv: list[str]) -> argparse.Namespace: Environment: AGENTFS_BIN path/name of agentfs executable AGENTFS_PROFILE set to 1 to collect AgentFS profile summaries - AGENTFS_OVERLAY_PARTIAL_ORIGIN - set to 1 to enable experimental partial-origin copy-up """, ) parser.add_argument( @@ -160,21 +157,20 @@ def parse_args(argv: list[str]) -> argparse.Namespace: default=env_flag("AGENTFS_PROFILE"), help="enable AGENTFS_PROFILE=1 for AgentFS invocations", ) - partial_origin_default = env_flag(PARTIAL_ORIGIN_ENV) partial_origin_group = parser.add_mutually_exclusive_group() partial_origin_group.add_argument( "--partial-origin", dest="partial_origin", action="store_true", - help=f"enable {PARTIAL_ORIGIN_ENV}=1 for AgentFS overlay invocations", + help="pass --partial-origin on to AgentFS overlay invocations", ) partial_origin_group.add_argument( "--no-partial-origin", dest="partial_origin", action="store_false", - help=f"disable {PARTIAL_ORIGIN_ENV} for AgentFS overlay invocations", + help="omit --partial-origin for AgentFS overlay invocations", ) - parser.set_defaults(partial_origin=partial_origin_default) + parser.set_defaults(partial_origin=False) parser.add_argument( "--keep-temp", action="store_true", @@ -488,16 +484,12 @@ def inspect_db(db_path: Path) -> dict[str, Any]: return {"inspectable": False, "reason": str(exc)} -def prepare_environment(temp_root: Path, profile: bool, partial_origin: bool) -> dict[str, str]: +def prepare_environment(temp_root: Path, profile: bool) -> dict[str, str]: env = os.environ.copy() env.setdefault("PYTHONDONTWRITEBYTECODE", "1") env.setdefault("NO_COLOR", "1") if profile: env["AGENTFS_PROFILE"] = "1" - if partial_origin: - env[PARTIAL_ORIGIN_ENV] = "1" - else: - env.pop(PARTIAL_ORIGIN_ENV, None) home = temp_root / "home" for path in (home, home / ".config", home / ".cache", home / ".local" / "share"): @@ -534,7 +526,7 @@ def main(argv: list[str]) -> int: result: dict[str, Any] try: agentfs_bin = resolve_agentfs_bin(args.agentfs_bin, repo_root) - env = prepare_environment(temp_root, args.profile, args.partial_origin) + env = prepare_environment(temp_root, args.profile) session = args.session or f"large-edit-{uuid.uuid4()}" source_root = temp_root / "source" @@ -546,12 +538,17 @@ def main(argv: list[str]) -> int: copy_base_tree(source_root, agentfs_base_root) db_path = Path(env["HOME"]) / ".agentfs" / "run" / session / "delta.db" - warmup_command = [ + agentfs_run_prefix = [ agentfs_bin, "run", "--session", session, "--no-default-allows", + ] + if args.partial_origin: + agentfs_run_prefix.extend(["--partial-origin", "on"]) + warmup_command = [ + *agentfs_run_prefix, "--", sys.executable, "-c", @@ -562,14 +559,7 @@ def main(argv: list[str]) -> int: inspect_before = inspect_db(db_path) native_command = [sys.executable, "-c", EDIT_WORKLOAD, "large.bin", str(offset)] - agentfs_command = [ - agentfs_bin, - "run", - "--session", - session, - "--no-default-allows", - "--", - ] + native_command + agentfs_command = [*agentfs_run_prefix, "--"] + native_command native = run_subprocess(native_command, native_root, env, args.timeout) agentfs = run_subprocess(agentfs_command, agentfs_base_root, env, args.timeout) @@ -622,9 +612,7 @@ def main(argv: list[str]) -> int: "db_path": str(db_path), "profile_enabled": args.profile, "partial_origin_enabled": args.partial_origin, - "env_flags": { - PARTIAL_ORIGIN_ENV: env.get(PARTIAL_ORIGIN_ENV), - }, + "partial_origin_cli": "on" if args.partial_origin else "omitted", "profile_summary_count": len(warmup["profile_summaries"]) + len(agentfs["profile_summaries"]), }, "database": { diff --git a/scripts/validation/lib/__init__.py b/scripts/validation/lib/__init__.py new file mode 100644 index 00000000..4fa37d40 --- /dev/null +++ b/scripts/validation/lib/__init__.py @@ -0,0 +1 @@ +"""Shared helpers for the scripts/validation harnesses.""" diff --git a/scripts/validation/lib/common.py b/scripts/validation/lib/common.py new file mode 100644 index 00000000..6e55f5cb --- /dev/null +++ b/scripts/validation/lib/common.py @@ -0,0 +1,313 @@ +"""Process, environment, and binary-resolution helpers shared by the +validation harnesses. + +Import via a sys.path bootstrap so the dash-named top-level scripts can use +the package from any CWD: + + sys.path.insert(0, str(Path(__file__).resolve().parent)) + from lib.common import resolve_agentfs_bin, run_subprocess +""" + +from __future__ import annotations + +import argparse +import json +import os +import shutil +import signal +import subprocess +import tempfile +import time +from collections.abc import MutableMapping +from pathlib import Path +from typing import Any, Optional + +OUTPUT_TAIL_CHARS = 8000 + + +def positive_int(value: str) -> int: + parsed = int(value) + if parsed < 1: + raise argparse.ArgumentTypeError("must be >= 1") + return parsed + + +def positive_float(value: str) -> float: + parsed = float(value) + if parsed <= 0: + raise argparse.ArgumentTypeError("must be > 0") + return parsed + + +def env_flag(name: str) -> bool: + value = os.environ.get(name, "") + return value.lower() in {"1", "true", "yes", "on"} + + +def pin_distro_git( + env: MutableMapping[str, str], scratch_dir: Path, home: Optional[Path] = None +) -> Path: + """Pin `git` to the distro binary for every subprocess of a harness. + + The user PATH may route `git` through a hook-manager shim that daemonizes + out of harness temp repos (library/environment.md). Prepend a shim dir + whose `git` symlinks the distro binary, and point the global git config at + a hookless file so no hook manager can re-enter via config. + + The PATH shim only holds for host-side processes: inside an `agentfs run` + sandbox, temp dirs and home files are hidden, so any shim dir silently + falls out of PATH and the hook-manager git takes over. That is what + ``env["GIT"]`` is for — it names the distro binary by absolute (system, + sandbox-visible) path, and every harness git invocation must honor it, + including the workload scripts spawned inside `agentfs run`. Pass ``home`` + to also write a hookless ``~/.gitconfig`` into a temp HOME (belt and + braces for host-side legs). + """ + shim_dir = scratch_dir / "git-shim" + shim_dir.mkdir(parents=True, exist_ok=True) + real_git = next( + (candidate for candidate in ("/usr/bin/git", "/bin/git") if os.access(candidate, os.X_OK)), + None, + ) or shutil.which("git") + if real_git is None: + raise RuntimeError("git executable is required") + shim = shim_dir / "git" + shim.unlink(missing_ok=True) + shim.symlink_to(real_git) + hookless = f"[core]\n\thooksPath = {shim_dir / 'hooks-none'}\n" + gitconfig = shim_dir / "gitconfig" + gitconfig.write_text(hookless, encoding="utf-8") + if home is not None: + (home / ".gitconfig").write_text(hookless, encoding="utf-8") + env["PATH"] = f"{shim_dir}{os.pathsep}{env.get('PATH', os.defpath)}" + env["GIT_CONFIG_GLOBAL"] = str(gitconfig) + env["GIT"] = str(real_git) + return shim + + +def git_ai_processes() -> dict[int, dict[str, Any]]: + """Live git-ai hook-manager processes, keyed by pid. + + Census guard for the pinned-git mechanism: an unpinned `git` daemonizes + `git-ai bg run` workers out of harness temp repos, and those outlive the + run. Take a snapshot before and after a harness run and diff them with + :func:`git_ai_leaks`. + """ + procs: dict[int, dict[str, Any]] = {} + for entry in Path("/proc").iterdir(): + if not entry.name.isdigit(): + continue + try: + raw = (entry / "cmdline").read_bytes() + except OSError: + continue + argv = [chunk.decode("utf-8", "replace") for chunk in raw.split(b"\0") if chunk] + if not (argv and any(Path(token).name == "git-ai" for token in argv[:2])): + continue + info: dict[str, Any] = { + "cmdline": " ".join(argv), + "home": None, + "agentfs_session": False, + } + try: + environ = (entry / "environ").read_bytes() + except OSError: + environ = b"" + for chunk in environ.split(b"\0"): + if chunk.startswith(b"HOME="): + info["home"] = chunk[len(b"HOME="):].decode("utf-8", "replace") + elif chunk.startswith(b"AGENTFS_SESSION="): + info["agentfs_session"] = True + procs[int(entry.name)] = info + return procs + + +def git_ai_leaks( + before: dict[int, dict[str, Any]], after: dict[int, dict[str, Any]] +) -> list[dict[str, Any]]: + """New git-ai processes attributable to the harness run. + + The user's own hook manager churns independently (its daemon respawns with + HOME under the real home dir), so a bare pid diff false-positives. Count + only new processes with a temp-dir HOME or an AGENTFS session in their + environment — the same discrimination rule library/environment.md requires + before killing one. + """ + tmp_prefix = tempfile.gettempdir().rstrip(os.sep) + os.sep + leaks: list[dict[str, Any]] = [] + for pid, info in sorted(after.items()): + if pid in before: + continue + home = info.get("home") or "" + if info.get("agentfs_session") or home.startswith(tmp_prefix): + leaks.append({"pid": pid, **info}) + return leaks + + +def tail_text(value: Any) -> str: + text = value.decode("utf-8", errors="replace") if isinstance(value, bytes) else str(value or "") + return text if len(text) <= OUTPUT_TAIL_CHARS else text[-OUTPUT_TAIL_CHARS:] + + +def terminate_process_tree(proc: subprocess.Popen[str]) -> None: + if proc.poll() is not None: + return + try: + os.killpg(proc.pid, signal.SIGTERM) + except ProcessLookupError: + return + except Exception: + proc.terminate() + try: + proc.wait(timeout=5) + return + except subprocess.TimeoutExpired: + pass + try: + os.killpg(proc.pid, signal.SIGKILL) + except ProcessLookupError: + return + except Exception: + proc.kill() + + +def run_subprocess(argv: list[str], cwd: Path, env: dict[str, str], timeout: float) -> dict[str, Any]: + started = time.perf_counter() + proc = subprocess.Popen( + argv, + cwd=str(cwd), + env=env, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + start_new_session=True, + ) + try: + stdout, stderr = proc.communicate(timeout=timeout) + timed_out = False + except subprocess.TimeoutExpired: + terminate_process_tree(proc) + try: + stdout, stderr = proc.communicate(timeout=5) + except subprocess.TimeoutExpired: + if proc.stdout is not None: + proc.stdout.close() + if proc.stderr is not None: + proc.stderr.close() + stdout, stderr = "", "process timed out; output pipes closed after termination" + timed_out = True + return { + "argv": argv, + "cwd": str(cwd), + "duration_seconds": time.perf_counter() - started, + "returncode": proc.returncode, + "timed_out": timed_out, + "stdout_tail": tail_text(stdout), + "stderr_tail": tail_text(stderr), + "stdout_bytes": len((stdout or "").encode("utf-8", errors="replace")), + "stderr_bytes": len((stderr or "").encode("utf-8", errors="replace")), + } + + +def parse_json_stdout(run: dict[str, Any]) -> Optional[dict[str, Any]]: + text = str(run.get("stdout_tail", "")).strip() + if text: + try: + value = json.loads(text) + if isinstance(value, dict): + return value + except json.JSONDecodeError: + start = text.find("{") + end = text.rfind("}") + if start >= 0 and end > start: + try: + value = json.loads(text[start : end + 1]) + if isinstance(value, dict): + return value + except json.JSONDecodeError: + pass + for line in reversed(text.splitlines()): + line = line.strip() + if not line: + continue + try: + value = json.loads(line) + except json.JSONDecodeError: + continue + if isinstance(value, dict): + return value + return None + + +def workspace_target_dir(repo_root: Path) -> Path: + """Resolve the cargo target dir; never hardcode per-crate target paths + (stale pre-workspace target dirs shadowed fixed binaries before).""" + proc = subprocess.run( + ["cargo", "metadata", "--format-version=1", "--no-deps"], + cwd=str(repo_root), + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + ) + if proc.returncode == 0: + try: + metadata = json.loads(proc.stdout) + target = metadata.get("target_directory") + if isinstance(target, str) and target: + return Path(target) + except json.JSONDecodeError: + pass + return repo_root / "target" + + +def resolve_agentfs_bin(agentfs_bin: Optional[str], repo_root: Path) -> str: + if agentfs_bin: + candidate = Path(agentfs_bin).expanduser() + if candidate.is_file() and os.access(candidate, os.X_OK): + return str(candidate.resolve()) + if os.sep not in agentfs_bin: + found = shutil.which(agentfs_bin) + if found: + return found + raise RuntimeError(f"configured agentfs executable not found or not executable: {agentfs_bin}") + + target_dir = workspace_target_dir(repo_root) + # Release first: it is what the gates measure and it is rebuilt more often + # during active development, so it is less likely to be stale. + for candidate in ( + target_dir / "release" / "agentfs", + target_dir / "debug" / "agentfs", + ): + if candidate.is_file() and os.access(candidate, os.X_OK): + return str(candidate) + + build = subprocess.run( + ["cargo", "build", "-p", "agentfs-cli", "--manifest-path", str(repo_root / "Cargo.toml")], + cwd=str(repo_root), + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + if build.returncode != 0: + raise RuntimeError( + "failed to build repo-local agentfs binary; set AGENTFS_BIN explicitly\n" + f"stdout:\n{tail_text(build.stdout)}\n" + f"stderr:\n{tail_text(build.stderr)}" + ) + built = target_dir / "debug" / "agentfs" + if built.is_file() and os.access(built, os.X_OK): + return str(built) + raise RuntimeError(f"repo-local build completed but binary was not found: {built}") + + +def git_commit(repo_root: Path) -> Optional[str]: + proc = subprocess.run( + [os.environ.get("GIT", "git"), "rev-parse", "HEAD"], + cwd=str(repo_root), + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + ) + if proc.returncode == 0: + return proc.stdout.strip() + return None diff --git a/scripts/validation/macos-nfs-git-validation.sh b/scripts/validation/macos-nfs-git-validation.sh index 241aa075..8fbd60ae 100755 --- a/scripts/validation/macos-nfs-git-validation.sh +++ b/scripts/validation/macos-nfs-git-validation.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash # -# Validate the macOS NFS path for git loose-object writes (#333). +# Validate the macOS NFS path for git loose-object writes (#333) and the +# `agentfs run` seatbelt read-scoping posture (secret outside the allow +# list must be unreadable; `--allow` must make it readable). # # Usage: # macos-nfs-git-validation.sh [--agentfs-bin PATH] [--report-dir DIR] [--keep-work] @@ -21,9 +23,13 @@ WORK_DIR="" MOUNT_DIR="" MOUNT_PID="" AGENTFS_RESOLVED="" +RUN_WORK_DIR="" +SECRET_DIR="" +RUN_SESSION_DENY="" +RUN_SESSION_ALLOW="" usage() { - sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//' + sed -n '2,14p' "$0" | sed 's/^# \{0,1\}//' } skip() { @@ -44,7 +50,7 @@ safe_rm_tmp() { local path="$1" [[ -n "$path" ]] || return 0 case "$path" in - /tmp/agentfs-macos-nfs-git-work.*|/tmp/agentfs-macos-nfs-git-mnt.*|/private/tmp/agentfs-macos-nfs-git-work.*|/private/tmp/agentfs-macos-nfs-git-mnt.*) + /tmp/agentfs-macos-nfs-git-work.*|/tmp/agentfs-macos-nfs-git-mnt.*|/private/tmp/agentfs-macos-nfs-git-work.*|/private/tmp/agentfs-macos-nfs-git-mnt.*|/tmp/agentfs-macos-read-scope-work.*|/private/tmp/agentfs-macos-read-scope-work.*) rm -rf -- "$path" ;; *) @@ -53,6 +59,32 @@ safe_rm_tmp() { esac } +safe_rm_secret_dir() { + local path="$1" + [[ -n "$path" ]] || return 0 + case "$path" in + */.agentfs-macos-read-scope.*) + rm -rf -- "$path" + ;; + *) + printf 'Refusing to remove non-harness secret path: %s\n' "$path" >&2 + ;; + esac +} + +safe_rm_run_session() { + local session="$1" + [[ -n "$session" ]] || return 0 + case "$session" in + macos-read-scope-*) + rm -rf -- "${HOME:?}/.agentfs/run/$session" + ;; + *) + printf 'Refusing to remove non-harness run session: %s\n' "$session" >&2 + ;; + esac +} + canonical_dir() { local path="$1" (cd "$path" && pwd -P) @@ -92,9 +124,17 @@ cleanup() { if [[ "$KEEP_WORK" -eq 0 ]]; then safe_rm_tmp "$WORK_DIR" safe_rm_tmp "$MOUNT_DIR" + safe_rm_tmp "$RUN_WORK_DIR" + safe_rm_secret_dir "$SECRET_DIR" + safe_rm_run_session "$RUN_SESSION_DENY" + safe_rm_run_session "$RUN_SESSION_ALLOW" elif [[ -n "$WORK_DIR" || -n "$MOUNT_DIR" ]]; then printf 'Kept work directory: %s\n' "$WORK_DIR" >&2 printf 'Kept mount directory: %s\n' "$MOUNT_DIR" >&2 + [[ -n "$RUN_WORK_DIR" ]] && printf 'Kept run work directory: %s\n' "$RUN_WORK_DIR" >&2 + [[ -n "$SECRET_DIR" ]] && printf 'Kept secret directory: %s\n' "$SECRET_DIR" >&2 + [[ -n "$RUN_SESSION_DENY" ]] && printf 'Kept run session: %s\n' "$HOME/.agentfs/run/$RUN_SESSION_DENY" >&2 + [[ -n "$RUN_SESSION_ALLOW" ]] && printf 'Kept run session: %s\n' "$HOME/.agentfs/run/$RUN_SESSION_ALLOW" >&2 fi exit "$status" @@ -227,4 +267,60 @@ if [[ "$git_status" -ne 0 ]]; then exit "$git_status" fi -printf 'macOS NFS git validation passed. Logs: %s\n' "$REPORT_DIR" +# --- agentfs run read-scoping leg ----------------------------------------- +# The darwin seatbelt profile is default-deny for reads: a secret outside the +# session/allow/platform roots must be unreadable, and `--allow` must make it +# readable (and writable, as before). + +printf 'Running agentfs run read-scoping checks...\n' + +RUN_WORK_DIR="$(canonical_dir "$(mktemp -d /tmp/agentfs-macos-read-scope-work.XXXXXX)")" +SECRET_DIR="$(canonical_dir "$(mktemp -d "$HOME/.agentfs-macos-read-scope.XXXXXX")")" +printf 'agentfs-read-scope-secret\n' >"$SECRET_DIR/secret.txt" + +RUN_SESSION_DENY="macos-read-scope-deny-$$-$(date +%s)" +RUN_SESSION_ALLOW="macos-read-scope-allow-$$-$(date +%s)" + +set +e +( + cd "$RUN_WORK_DIR" + "$AGENTFS_RESOLVED" run --session "$RUN_SESSION_DENY" \ + /bin/cat "$SECRET_DIR/secret.txt" +) >"$REPORT_DIR/read-scope-deny.log" 2>&1 +deny_status=$? +set -e + +if ! grep -q 'Welcome to AgentFS' "$REPORT_DIR/read-scope-deny.log"; then + printf 'FAILED: agentfs run never reached the sandbox (mount failure?). See %s/read-scope-deny.log\n' "$REPORT_DIR" >&2 + exit 1 +fi +if [[ "$deny_status" -eq 0 ]]; then + printf 'FAILED: read of %s/secret.txt outside the allow list unexpectedly succeeded. See %s/read-scope-deny.log\n' "$SECRET_DIR" "$REPORT_DIR" >&2 + exit 1 +fi +if grep -q 'agentfs-read-scope-secret' "$REPORT_DIR/read-scope-deny.log"; then + printf 'FAILED: secret content leaked despite exit status %s. See %s/read-scope-deny.log\n' "$deny_status" "$REPORT_DIR" >&2 + exit 1 +fi +if ! grep -Eqi 'operation not permitted|permission denied' "$REPORT_DIR/read-scope-deny.log"; then + printf 'FAILED: expected a permission error from the sandboxed read; got exit %s without one. See %s/read-scope-deny.log\n' "$deny_status" "$REPORT_DIR" >&2 + exit 1 +fi +printf 'Read of unallowed path denied as expected (exit %s).\n' "$deny_status" + +set +e +( + cd "$RUN_WORK_DIR" + "$AGENTFS_RESOLVED" run --session "$RUN_SESSION_ALLOW" --allow "$SECRET_DIR" \ + /bin/cat "$SECRET_DIR/secret.txt" +) >"$REPORT_DIR/read-scope-allow.log" 2>&1 +allow_status=$? +set -e + +if [[ "$allow_status" -ne 0 ]] || ! grep -q 'agentfs-read-scope-secret' "$REPORT_DIR/read-scope-allow.log"; then + printf 'FAILED: --allow %s did not make the secret readable (exit %s). See %s/read-scope-allow.log\n' "$SECRET_DIR" "$allow_status" "$REPORT_DIR" >&2 + exit 1 +fi +printf 'Read of --allow path succeeded as expected.\n' + +printf 'macOS NFS git + run read-scoping validation passed. Logs: %s\n' "$REPORT_DIR" diff --git a/scripts/validation/noopen-coherence.py b/scripts/validation/noopen-coherence.py index 466e7460..dc85d5cb 100644 --- a/scripts/validation/noopen-coherence.py +++ b/scripts/validation/noopen-coherence.py @@ -41,7 +41,8 @@ from pathlib import Path from typing import Any, Optional -OUTPUT_TAIL_CHARS = 8000 +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from lib.common import resolve_agentfs_bin, tail_text # noqa: E402 EXEC_WORKLOAD = r''' import ctypes @@ -160,29 +161,6 @@ def check(label, observed, expected): ''' -def tail_text(text: str) -> str: - return text if len(text) <= OUTPUT_TAIL_CHARS else text[-OUTPUT_TAIL_CHARS:] - - -def resolve_agentfs_bin(agentfs_bin: Optional[str], repo_root: Path) -> str: - if agentfs_bin: - candidate = Path(agentfs_bin).expanduser() - if candidate.is_file() and os.access(candidate, os.X_OK): - return str(candidate.resolve()) - if os.sep not in agentfs_bin: - found = shutil.which(agentfs_bin) - if found: - return found - raise RuntimeError(f"agentfs binary not found or not executable: {agentfs_bin}") - for candidate in ( - repo_root / "cli" / "target" / "release" / "agentfs", - repo_root / "cli" / "target" / "debug" / "agentfs", - ): - if candidate.is_file() and os.access(candidate, os.X_OK): - return str(candidate) - raise RuntimeError("no agentfs binary found; pass --agentfs-bin or set AGENTFS_BIN") - - def parse_workload_json(stdout: str) -> Optional[dict[str, Any]]: for line in reversed(stdout.splitlines()): line = line.strip() @@ -199,7 +177,7 @@ def parse_workload_json(stdout: str) -> Optional[dict[str, Any]]: def parse_fuse_counters(output: str) -> Optional[dict[str, Any]]: for line in reversed(output.splitlines()): - if '"agentfs_profile_summary"' not in line or '"fuse_session"' not in line: + if '"agentfs_profile_summary"' not in line: continue start = line.find("{") if start < 0: @@ -209,7 +187,7 @@ def parse_fuse_counters(output: str) -> Optional[dict[str, Any]]: except json.JSONDecodeError: continue counters = value.get("counters") - if isinstance(counters, dict): + if isinstance(counters, dict) and any(key.startswith("fuse_") for key in counters): return counters return None @@ -257,6 +235,38 @@ def run_one( return result +def cleanup_run_session_dir(session_id: str) -> None: + """Remove exactly the session dir a run leg created. + + Never sweeps ~/.agentfs/run wholesale (the user may have real sessions + there), and never raises: this runs in a finally block, so an exception + here would mask the leg's real failure. If the leg died with its FUSE + mount still live or wedged, stats under the mountpoint fail (ENOTCONN) + and a bare rmtree would silently leave the dir behind — detach the mount + first so the dir actually goes away. + """ + session_dir = Path.home() / ".agentfs" / "run" / session_id + mountpoint = session_dir / "mnt" + try: + os.stat(mountpoint) + needs_unmount = os.path.ismount(mountpoint) + except FileNotFoundError: + needs_unmount = False + except OSError: + needs_unmount = True + if needs_unmount: + try: + subprocess.run( + ["fusermount3", "-uz", str(mountpoint)], + check=False, + capture_output=True, + timeout=10, + ) + except (OSError, subprocess.SubprocessError): + pass + shutil.rmtree(session_dir, ignore_errors=True) + + def base_env(extra: dict[str, str]) -> dict[str, str]: env = os.environ.copy() env["AGENTFS_PROFILE"] = "1" @@ -319,20 +329,24 @@ def main() -> int: base_root = temp_root / f"{label}-base" base_root.mkdir() (base_root / "base.txt").write_bytes(b"base-content\n") + session_id = f"noopen-coh-{uuid.uuid4().hex[:8]}" argv = [ agentfs_bin, "run", "--session", - f"noopen-coh-{uuid.uuid4().hex[:8]}", + session_id, "--no-default-allows", "--", sys.executable, "-c", OVERLAY_WORKLOAD, ] - runs.append( - run_one(argv, base_root, base_env(extra), args.timeout, label, noopen) - ) + try: + runs.append( + run_one(argv, base_root, base_env(extra), args.timeout, label, noopen) + ) + finally: + cleanup_run_session_dir(session_id) resolutions = sum(r.get("fuse_ino_file_resolutions") or 0 for r in runs if r["noopen"]) upgrades = sum(r.get("fuse_ino_file_upgrades") or 0 for r in runs if r["noopen"]) diff --git a/scripts/validation/partial-origin-no-real-write.py b/scripts/validation/partial-origin-no-real-write.py index 6f2e2926..500532f7 100755 --- a/scripts/validation/partial-origin-no-real-write.py +++ b/scripts/validation/partial-origin-no-real-write.py @@ -21,7 +21,6 @@ OUTPUT_TAIL_CHARS = 4000 ONE_MIB = 1024 * 1024 -PARTIAL_ORIGIN_ENV = "AGENTFS_OVERLAY_PARTIAL_ORIGIN" WRITE_WORKLOAD = r''' @@ -437,7 +436,6 @@ def prepare_environment(temp_root: Path, profile: bool) -> dict[str, str]: env.setdefault("NO_COLOR", "1") if profile: env["AGENTFS_PROFILE"] = "1" - env[PARTIAL_ORIGIN_ENV] = "1" home = temp_root / "home" for path in (home, home / ".config", home / ".cache", home / ".local" / "share"): @@ -487,6 +485,8 @@ def main(argv: list[str]) -> int: "--session", session, "--no-default-allows", + "--partial-origin", + "on", "--", sys.executable, "-c", @@ -536,8 +536,8 @@ def main(argv: list[str]) -> int: "session": session, "db_path": str(db_path), "profile_enabled": args.profile, + "partial_origin_cli": "on", "partial_origin_enabled": True, - "env_flags": {PARTIAL_ORIGIN_ENV: env.get(PARTIAL_ORIGIN_ENV)}, "profile_summary_count": len(run["profile_summaries"]), }, "database": { diff --git a/scripts/validation/phase7-validation.py b/scripts/validation/phase7-validation.py index 2f0a0bff..a0e75f42 100755 --- a/scripts/validation/phase7-validation.py +++ b/scripts/validation/phase7-validation.py @@ -382,7 +382,6 @@ def child_env(agentfs_bin: str, output_dir: Path) -> dict[str, str]: env.setdefault("PYTHONDONTWRITEBYTECODE", "1") env.setdefault("NO_COLOR", "1") env["AGENTFS_BIN"] = agentfs_bin - env.pop("AGENTFS_OVERLAY_PARTIAL_ORIGIN", None) child_tmp = output_dir / "child-tmp" child_tmp.mkdir(parents=True, exist_ok=True) env["TMPDIR"] = str(child_tmp) diff --git a/scripts/validation/phase8-concurrent-git-stress.py b/scripts/validation/phase8-concurrent-git-stress.py index c8766258..ee2c3201 100755 --- a/scripts/validation/phase8-concurrent-git-stress.py +++ b/scripts/validation/phase8-concurrent-git-stress.py @@ -23,6 +23,9 @@ from pathlib import Path from typing import Any, Optional +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from lib import common # noqa: E402 + OUTPUT_TAIL_CHARS = 8000 HASH_BLOCK_BYTES = 1024 * 1024 @@ -59,9 +62,13 @@ def git_env(): def run_git(label, argv, cwd): + # Honor the harness's pinned-git override: PATH shims are invisible inside + # the agentfs sandbox, so only an absolute system-path GIT avoids the + # daemonizing hook-manager shim (scripts/validation/lib/common.py). + git = os.environ.get("GIT", "git") started = time.perf_counter() proc = subprocess.run( - ["git"] + argv, + [git] + argv, cwd=str(cwd), env=git_env(), text=True, @@ -77,7 +84,7 @@ def run_git(label, argv, cwd): digest.update(stdout.encode("utf-8", errors="replace")) return { "label": label, - "argv": ["git"] + argv, + "argv": [git] + argv, "returncode": proc.returncode, "duration_seconds": time.perf_counter() - started, "stdout_sha256": hashlib.sha256(stdout.encode("utf-8", errors="replace")).hexdigest(), @@ -99,7 +106,7 @@ def mutate_fixture(root, edit_files, append_bytes): ls_files = run_git("ls_files_for_mutation", ["ls-files", "-z"], root) require_ok(ls_files, "git ls-files") proc = subprocess.run( - ["git", "ls-files", "-z"], + [os.environ.get("GIT", "git"), "ls-files", "-z"], cwd=str(root), env=git_env(), text=True, @@ -365,7 +372,7 @@ def resolve_agentfs_bin(agentfs_bin: Optional[str], repo_root: Path) -> str: def git_commit(repo_root: Path) -> Optional[str]: proc = subprocess.run( - ["git", "rev-parse", "HEAD"], + [os.environ.get("GIT", "git"), "rev-parse", "HEAD"], cwd=str(repo_root), text=True, stdout=subprocess.PIPE, @@ -390,10 +397,11 @@ def git_env() -> dict[str, str]: def run_git(argv: list[str], cwd: Path, *, env: Optional[dict[str, str]] = None, timeout: float = 60) -> subprocess.CompletedProcess[str]: + resolved_env = env or git_env() return subprocess.run( - ["git"] + argv, + [resolved_env.get("GIT", "git")] + argv, cwd=str(cwd), - env=env or git_env(), + env=resolved_env, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -479,6 +487,7 @@ def prepare_environment(temp_root: Path, profile: bool) -> dict[str, str]: env["XDG_CONFIG_HOME"] = str(home / ".config") env["XDG_CACHE_HOME"] = str(home / ".cache") env["XDG_DATA_HOME"] = str(home / ".local" / "share") + common.pin_distro_git(env, home, home=home) tmp = temp_root / "tmp" tmp.mkdir(parents=True, exist_ok=True) @@ -647,7 +656,8 @@ def main(argv: list[str]) -> int: exit_code = 0 result: dict[str, Any] try: - if shutil.which("git") is None: + common.pin_distro_git(os.environ, temp_root) + if shutil.which(os.environ.get("GIT", "git")) is None: raise RuntimeError("git executable is required") agentfs_bin = resolve_agentfs_bin(args.agentfs_bin, repo_root) env = prepare_environment(temp_root, args.profile) diff --git a/scripts/validation/phase8-validation.py b/scripts/validation/phase8-validation.py index b083c643..b03f3996 100755 --- a/scripts/validation/phase8-validation.py +++ b/scripts/validation/phase8-validation.py @@ -10,7 +10,6 @@ from __future__ import annotations import argparse -import importlib.util import json import os import shutil @@ -21,18 +20,8 @@ from pathlib import Path from typing import Any, Optional - -def load_common() -> Any: - common_path = Path(__file__).with_name("phase8-writeback-durability.py") - spec = importlib.util.spec_from_file_location("phase8_writeback_durability_common", common_path) - if spec is None or spec.loader is None: - raise RuntimeError(f"failed to load common helpers from {common_path}") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -common = load_common() +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from lib import common # noqa: E402 def env_float(name: str, default: float) -> float: @@ -338,6 +327,8 @@ def main(argv: list[str]) -> int: exit_code = 0 result: dict[str, Any] try: + git_ai_before = common.git_ai_processes() + pinned_git = common.pin_distro_git(os.environ, output_dir) agentfs_bin = common.resolve_agentfs_bin(args.agentfs_bin, repo_root) env = child_env(agentfs_bin, output_dir) scripts = repo_root / "scripts" / "validation" @@ -460,6 +451,49 @@ def main(argv: list[str]) -> int: enforce=enforce_phase8, ) + # FUSE coherence harnesses for the default-on noopen/noflush semantics + # and external base drift; folded into the Phase 8 gate per the M7 + # scripts consolidation (scripts-docs-sweep.md recommendation 1). Each + # runs its own config matrix, so wall timeouts are per-harness. + coherence_wall_timeout = max(args.timeout * 4, 300.0) + gates["noopen_coherence"] = run_json_gate( + "noopen-coherence", + scripts / "noopen-coherence.py", + ["--agentfs-bin", agentfs_bin], + repo_root, + env, + coherence_wall_timeout, + output_dir, + ) + gates["flush_coherence"] = run_json_gate( + "flush-coherence", + scripts / "flush-coherence.py", + ["--agentfs-bin", agentfs_bin], + repo_root, + env, + coherence_wall_timeout, + output_dir, + ) + gates["external_base_mutation_coherence"] = run_json_gate( + "external-base-mutation-coherence", + scripts / "external-base-mutation-coherence.py", + ["--agentfs-bin", agentfs_bin], + repo_root, + env, + coherence_wall_timeout, + output_dir, + ) + + leaked_git_ai = common.git_ai_leaks(git_ai_before, common.git_ai_processes()) + gates["git_ai_process_census"] = { + "name": "git-ai-process-census", + "status": "failed" if leaked_git_ai else "passed", + "required": True, + "pinned_git": str(pinned_git), + "pre_existing_count": len(git_ai_before), + "leaked": leaked_git_ai, + } + failed_gates = [name for name, gate in gates.items() if not gate_passed(gate)] threshold_failures = [] parallel_failures = [] diff --git a/scripts/validation/phase8-writeback-durability.py b/scripts/validation/phase8-writeback-durability.py index 3318d457..78769073 100755 --- a/scripts/validation/phase8-writeback-durability.py +++ b/scripts/validation/phase8-writeback-durability.py @@ -23,30 +23,22 @@ from pathlib import Path from typing import Any, Optional +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from lib.common import ( # noqa: E402 + env_flag, + git_commit, + parse_json_stdout, + positive_float, + positive_int, + resolve_agentfs_bin, + run_subprocess, + tail_text, + terminate_process_tree, +) -OUTPUT_TAIL_CHARS = 8000 HASH_BLOCK_BYTES = 1024 * 1024 -def positive_int(value: str) -> int: - parsed = int(value) - if parsed < 1: - raise argparse.ArgumentTypeError("must be >= 1") - return parsed - - -def positive_float(value: str) -> float: - parsed = float(value) - if parsed <= 0: - raise argparse.ArgumentTypeError("must be > 0") - return parsed - - -def env_flag(name: str) -> bool: - value = os.environ.get(name, "") - return value.lower() in {"1", "true", "yes", "on"} - - def parse_args(argv: list[str]) -> argparse.Namespace: parser = argparse.ArgumentParser( description="Verify fsynced AgentFS writes survive mount SIGKILL and remount." @@ -69,151 +61,6 @@ def parse_args(argv: list[str]) -> argparse.Namespace: return parser.parse_args(argv) -def tail_text(value: Any) -> str: - text = value.decode("utf-8", errors="replace") if isinstance(value, bytes) else str(value or "") - return text if len(text) <= OUTPUT_TAIL_CHARS else text[-OUTPUT_TAIL_CHARS:] - - -def terminate_process_tree(proc: subprocess.Popen[str]) -> None: - if proc.poll() is not None: - return - try: - os.killpg(proc.pid, signal.SIGTERM) - except ProcessLookupError: - return - except Exception: - proc.terminate() - try: - proc.wait(timeout=5) - return - except subprocess.TimeoutExpired: - pass - try: - os.killpg(proc.pid, signal.SIGKILL) - except ProcessLookupError: - return - except Exception: - proc.kill() - - -def run_subprocess(argv: list[str], cwd: Path, env: dict[str, str], timeout: float) -> dict[str, Any]: - started = time.perf_counter() - proc = subprocess.Popen( - argv, - cwd=str(cwd), - env=env, - text=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - start_new_session=True, - ) - try: - stdout, stderr = proc.communicate(timeout=timeout) - timed_out = False - except subprocess.TimeoutExpired: - terminate_process_tree(proc) - try: - stdout, stderr = proc.communicate(timeout=5) - except subprocess.TimeoutExpired: - if proc.stdout is not None: - proc.stdout.close() - if proc.stderr is not None: - proc.stderr.close() - stdout, stderr = "", "process timed out; output pipes closed after termination" - timed_out = True - return { - "argv": argv, - "cwd": str(cwd), - "duration_seconds": time.perf_counter() - started, - "returncode": proc.returncode, - "timed_out": timed_out, - "stdout_tail": tail_text(stdout), - "stderr_tail": tail_text(stderr), - "stdout_bytes": len((stdout or "").encode("utf-8", errors="replace")), - "stderr_bytes": len((stderr or "").encode("utf-8", errors="replace")), - } - - -def parse_json_stdout(run: dict[str, Any]) -> Optional[dict[str, Any]]: - text = str(run.get("stdout_tail", "")).strip() - if text: - try: - value = json.loads(text) - if isinstance(value, dict): - return value - except json.JSONDecodeError: - start = text.find("{") - end = text.rfind("}") - if start >= 0 and end > start: - try: - value = json.loads(text[start : end + 1]) - if isinstance(value, dict): - return value - except json.JSONDecodeError: - pass - for line in reversed(text.splitlines()): - line = line.strip() - if not line: - continue - try: - value = json.loads(line) - except json.JSONDecodeError: - continue - if isinstance(value, dict): - return value - return None - - -def resolve_agentfs_bin(agentfs_bin: Optional[str], repo_root: Path) -> str: - if agentfs_bin: - candidate = Path(agentfs_bin).expanduser() - if candidate.is_file() and os.access(candidate, os.X_OK): - return str(candidate.resolve()) - if os.sep not in agentfs_bin: - found = shutil.which(agentfs_bin) - if found: - return found - raise RuntimeError(f"configured agentfs executable not found or not executable: {agentfs_bin}") - - for candidate in ( - repo_root / "cli" / "target" / "debug" / "agentfs", - repo_root / "cli" / "target" / "release" / "agentfs", - ): - if candidate.is_file() and os.access(candidate, os.X_OK): - return str(candidate) - - build = subprocess.run( - ["cargo", "build", "--manifest-path", str(repo_root / "cli" / "Cargo.toml")], - cwd=str(repo_root / "cli"), - text=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - if build.returncode != 0: - raise RuntimeError( - "failed to build repo-local agentfs binary; set AGENTFS_BIN explicitly\n" - f"stdout:\n{tail_text(build.stdout)}\n" - f"stderr:\n{tail_text(build.stderr)}" - ) - built = repo_root / "cli" / "target" / "debug" / "agentfs" - if built.is_file() and os.access(built, os.X_OK): - return str(built) - raise RuntimeError(f"repo-local build completed but binary was not found: {built}") - - -def git_commit(repo_root: Path) -> Optional[str]: - proc = subprocess.run( - ["git", "rev-parse", "HEAD"], - cwd=str(repo_root), - text=True, - stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, - ) - if proc.returncode == 0: - return proc.stdout.strip() - return None - - def prepare_environment(temp_root: Path) -> dict[str, str]: env = os.environ.copy() env.setdefault("PYTHONDONTWRITEBYTECODE", "1") diff --git a/scripts/validation/posix/run-pjdfstest.sh b/scripts/validation/posix/run-pjdfstest.sh index d54353ce..b80153a1 100755 --- a/scripts/validation/posix/run-pjdfstest.sh +++ b/scripts/validation/posix/run-pjdfstest.sh @@ -12,7 +12,6 @@ # AGENTFS_BIN agentfs executable to invoke (default: agentfs). # PJDFSTEST_PROFILE test profile to run (default: full). # PJDFSTEST_MANIFEST explicit manifest overriding --profile. -# AGENTFS_OVERLAY_PARTIAL_ORIGIN enable partial-origin overlay mode when true/1. # REPORT_DIR directory where logs should be written. # SKIP_CODE exit code for missing prerequisites (default: 77). # @@ -24,7 +23,7 @@ PJDFSTEST_DIR="${PJDFSTEST_DIR:-}" PJDFSTEST_PROFILE="${PJDFSTEST_PROFILE:-full}" PJDFSTEST_MANIFEST="${PJDFSTEST_MANIFEST:-}" PJDFSTEST_KNOWN_UNSUPPORTED="${PJDFSTEST_KNOWN_UNSUPPORTED:-}" -PARTIAL_ORIGIN="${AGENTFS_OVERLAY_PARTIAL_ORIGIN:-}" +PARTIAL_ORIGIN="" REPORT_DIR="${REPORT_DIR:-}" KEEP_WORK=0 @@ -47,7 +46,7 @@ usage() { print_testing_guidance() { cat >&2 <<'EOF' -Relevant setup guidance from TESTING.md: +Relevant setup guidance from docs/TESTING.md: ## pjdfstest @@ -62,11 +61,11 @@ command -v prove command -v pjdfstest ``` -AgentFS harness command from TESTING.md: +AgentFS harness command from docs/TESTING.md: ```bash scripts/validation/posix/run-pjdfstest.sh \ - --agentfs-bin "$PWD/cli/target/debug/agentfs" \ + --agentfs-bin "$PWD/target/debug/agentfs" \ --pjdfstest-dir /path/to/pjdfstest \ --profile phase45-ci ``` @@ -82,7 +81,10 @@ skip_missing() { resolve_agentfs() { if [[ "$AGENTFS_BIN" == */* ]]; then [[ -x "$AGENTFS_BIN" ]] || return 1 - AGENTFS_RESOLVED="$AGENTFS_BIN" + # Absolutize: the harness cd's into its work and mount dirs before + # invoking the binary, which silently breaks a relative path (the + # exit-127 error only lands in init.log). + AGENTFS_RESOLVED="$(cd "$(dirname "$AGENTFS_BIN")" && pwd)/$(basename "$AGENTFS_BIN")" else AGENTFS_RESOLVED="$(command -v "$AGENTFS_BIN" 2>/dev/null)" || return 1 fi @@ -378,16 +380,14 @@ printf 'pjdfstest binary: %s\n' "$PJDFSTEST_RESOLVED" printf 'pjdfstest tests: %s\n' "$PJDFSTEST_TESTS" printf 'pjdfstest profile: %s\n' "$PJDFSTEST_PROFILE" if env_flag_enabled "$PARTIAL_ORIGIN"; then - export AGENTFS_OVERLAY_PARTIAL_ORIGIN=1 - printf 'partial-origin overlay: enabled\n' + printf 'partial-origin overlay: enabled via --partial-origin on\n' else - unset AGENTFS_OVERLAY_PARTIAL_ORIGIN printf 'partial-origin overlay: disabled\n' fi printf 'Report directory: %s\n' "$REPORT_DIR" printf '%s\n' "$PJDFSTEST_PROFILE" >"$REPORT_DIR/selected-profile.txt" -printf '%s\n' "${AGENTFS_OVERLAY_PARTIAL_ORIGIN:-}" >"$REPORT_DIR/partial-origin-env.txt" +printf '%s\n' "$(env_flag_enabled "$PARTIAL_ORIGIN" && printf 'on' || printf 'off')" >"$REPORT_DIR/partial-origin-cli.txt" if [[ -n "$PJDFSTEST_RESOLVED_MANIFEST" ]]; then { printf 'path\t%s\n' "$PJDFSTEST_RESOLVED_MANIFEST" @@ -422,7 +422,11 @@ if [[ ! -f "$DB_PATH" ]]; then exit 1 fi -"$AGENTFS_RESOLVED" mount "$DB_PATH" "$MOUNT_DIR" --foreground >"$REPORT_DIR/mount.log" 2>&1 & +MOUNT_CMD=("$AGENTFS_RESOLVED" mount "$DB_PATH" "$MOUNT_DIR" --foreground) +if env_flag_enabled "$PARTIAL_ORIGIN"; then + MOUNT_CMD+=(--partial-origin on) +fi +"${MOUNT_CMD[@]}" >"$REPORT_DIR/mount.log" 2>&1 & MOUNT_PID=$! mounted=0 diff --git a/scripts/validation/replay/replay_workload.py b/scripts/validation/replay/replay_workload.py index 04c65505..4a62bc9c 100755 --- a/scripts/validation/replay/replay_workload.py +++ b/scripts/validation/replay/replay_workload.py @@ -541,7 +541,7 @@ def resolve_agentfs(agentfs_bin: str) -> str: if not resolved: raise PrerequisiteSkip( "agentfs binary not found. Build/install it first, or pass --agentfs-bin PATH " - "(TESTING.md: cd cli && cargo build --release && cp target/release/agentfs /usr/local/bin)." + "(for example: cargo build --release -p agentfs-cli --bins && cp target/release/agentfs /usr/local/bin)." ) return resolved diff --git a/sdk/go/README.md b/sdk/go/README.md deleted file mode 100644 index 11659c70..00000000 --- a/sdk/go/README.md +++ /dev/null @@ -1,512 +0,0 @@ -# AgentFS Go SDK - -A Go SDK for AgentFS - a SQLite-backed virtual filesystem, key-value store, and tool call tracking system for AI agents. - -## Installation - -```bash -go get github.com/tursodatabase/agentfs/sdk/go -``` - -## Quick Start - -```go -package main - -import ( - "context" - "fmt" - "log" - "time" - - agentfs "github.com/tursodatabase/agentfs/sdk/go" -) - -func main() { - ctx := context.Background() - - // Open or create an AgentFS database - afs, err := agentfs.Open(ctx, agentfs.AgentFSOptions{ - ID: "my-agent", // Creates ~/.agentfs/my-agent.db - }) - if err != nil { - log.Fatal(err) - } - defer afs.Close() - - // === Filesystem Operations === - - // Write a file - err = afs.FS.WriteFile(ctx, "/hello.txt", []byte("Hello, World!"), 0o644) - if err != nil { - log.Fatal(err) - } - - // Read a file - data, err := afs.FS.ReadFile(ctx, "/hello.txt") - if err != nil { - log.Fatal(err) - } - fmt.Println(string(data)) // "Hello, World!" - - // Create directories - err = afs.FS.MkdirAll(ctx, "/path/to/dir", 0o755) - - // List directory - names, err := afs.FS.Readdir(ctx, "/") - for _, name := range names { - fmt.Printf("File name: %s\n", name) - } - - // Get file stats - stats, err := afs.FS.Stat(ctx, "/hello.txt") - fmt.Printf("Size: %d, IsDir: %v\n", stats.Size, stats.IsDir()) - - // === Key-Value Store === - - // Store values (JSON-serialized) - err = afs.KV.Set(ctx, "config:version", "1.0.0") - - type Settings struct { - Theme string `json:"theme"` - FontSize int `json:"font_size"` - } - err = afs.KV.Set(ctx, "user:settings", Settings{Theme: "dark", FontSize: 14}) - - // Retrieve values - var version string - err = afs.KV.Get(ctx, "config:version", &version) - - var settings Settings - err = afs.KV.Get(ctx, "user:settings", &settings) - - // List keys by prefix - keys, err := afs.KV.Keys(ctx, "config:") - for _, key := range keys { - fmt.Printf("Key: %s\n", key) - } - - // === Tool Call Tracking === - - // Start/Success pattern - pending, err := afs.Tools.Start(ctx, "web_search", map[string]string{ - "query": "golang sqlite", - }) - - // ... perform operation ... - - call, err := pending.Success(ctx, map[string]any{ - "results": []string{"result1", "result2"}, - }) - fmt.Printf("Tool call %d completed in %dms\n", call.ID, call.DurationMs) - - // Or record directly - now := time.Now().Unix() - call, err = afs.Tools.Record(ctx, "read_file", - map[string]string{"path": "/test.txt"}, - "file contents", - nil, // no error - now-1, now, - ) - - // Query tool calls - calls, err := afs.Tools.GetByName(ctx, "web_search", 10) - stats, err := afs.Tools.GetStats(ctx) -} -``` - -## API Reference - -### AgentFS - -```go -// Open creates or opens an AgentFS database -func Open(ctx context.Context, opts AgentFSOptions) (*AgentFS, error) - -type AgentFSOptions struct { - ID string // Agent ID (creates ~/.agentfs/{id}.db) - Path string // Explicit database path (takes precedence) - ChunkSize int // Chunk size for file data (default: 4096) - Pool PoolOptions // Connection pool configuration -} - -type PoolOptions struct { - MaxOpenConns int // Max open connections (0 = unlimited) - MaxIdleConns int // Max idle connections (default: 2) - ConnMaxLifetime time.Duration // Max connection lifetime (0 = forever) - ConnMaxIdleTime time.Duration // Max idle time (0 = forever) -} -``` - -### Filesystem - -| Method | Description | -|-------------------------------|-------------------------------| -| `Stat(path)` | Get file/directory metadata | -| `Readdir(path)` | List directory entries | -| `ReaddirPlus(path)` | List entries with stats | -| `Mkdir(path, mode)` | Create directory | -| `MkdirAll(path, mode)` | Create directory and parents | -| `ReadFile(path)` | Read entire file | -| `WriteFile(path, data, mode)` | Write file (creates parents) | -| `Unlink(path)` | Delete file | -| `Rmdir(path)` | Delete empty directory | -| `Rename(old, new)` | Move/rename file or directory | -| `Link(existing, new)` | Create hard link | -| `Symlink(target, link)` | Create symbolic link | -| `Readlink(path)` | Read symlink target | -| `Chmod(path, mode)` | Change permissions | -| `Utimes(path, atime, mtime)` | Update timestamps (seconds) | -| `UtimesNano(path, ...)` | Update timestamps (nanoseconds) | -| `Open(path, flags)` | Open file handle | -| `Create(path, mode)` | Create new file handle | - -### File Handle - -The `File` type implements Go's standard I/O interfaces for seamless integration: - -```go -// File implements: -// - io.Reader, io.Writer (sequential I/O) -// - io.ReaderAt, io.WriterAt (random access) -// - io.Seeker, io.Closer - -// Sequential I/O (tracks position automatically) -f, _ := afs.FS.Open(ctx, "/file.txt", agentfs.O_RDWR) -defer f.Close() - -data, _ := io.ReadAll(f) // Read entire file -f.Seek(0, io.SeekStart) // Seek back to start -f.Write([]byte("new content")) // Write at current position - -// Random access (does not affect position) -f.ReadAt(buf, 100) // Read at offset 100 -f.WriteAt(data, 200) // Write at offset 200 - -// Context-aware positioned I/O -f.Pread(ctx, buf, offset) // Read with context -f.Pwrite(ctx, data, offset) // Write with context - -// Use with io.Copy -io.Copy(destFile, srcFile) // Copy between files -io.Copy(f, bytes.NewReader(data)) // Write from bytes.Reader -``` - -| Method | Description | -|-----------------------------|--------------------------------| -| `Read(p []byte)` | Read sequentially (io.Reader) | -| `Write(p []byte)` | Write sequentially (io.Writer) | -| `Seek(offset, whence)` | Set position (io.Seeker) | -| `ReadAt(p, offset)` | Read at offset (io.ReaderAt) | -| `WriteAt(p, offset)` | Write at offset (io.WriterAt) | -| `Pread(ctx, buf, offset)` | Positioned read with context | -| `Pwrite(ctx, data, offset)` | Positioned write with context | -| `Truncate(ctx, size)` | Set file size | -| `Stat(ctx)` | Get metadata | -| `Size()` | Get current file size | -| `Offset()` | Get current position | -| `Close()` | Close handle | - -### Stats Struct - -The `Stats` struct returned by `Stat()` and `ReaddirPlus()` includes nanosecond-precision timestamps (SPEC v0.4): - -```go -type Stats struct { - Ino int64 // Inode number - Mode int64 // File type and permissions - Nlink int64 // Number of hard links - UID int64 // Owner user ID - GID int64 // Owner group ID - Size int64 // File size in bytes - Atime int64 // Last access time (seconds) - Mtime int64 // Last modification time (seconds) - Ctime int64 // Change time (seconds) - Rdev int64 // Device number (for special files) - AtimeNsec int64 // Nanosecond component of atime (0-999999999) - MtimeNsec int64 // Nanosecond component of mtime (0-999999999) - CtimeNsec int64 // Nanosecond component of ctime (0-999999999) -} - -// Helper methods for full time.Time values -stats.AtimeTime() // Returns time.Time with nanosecond precision -stats.MtimeTime() // Returns time.Time with nanosecond precision -stats.CtimeTime() // Returns time.Time with nanosecond precision - -// File type helpers -stats.IsDir() // Is directory? -stats.IsRegularFile() // Is regular file? -stats.IsSymlink() // Is symbolic link? -stats.IsFIFO() // Is FIFO/named pipe? -stats.IsCharDevice() // Is character device? -stats.IsBlockDevice() // Is block device? -stats.IsSocket() // Is socket? -``` - -### Key-Value Store - -| Method | Description | -|-------------------|------------------------------------| -| `Set(key, value)` | Store value (JSON-serialized) | -| `Get(key, dest)` | Retrieve and unmarshal value | -| `GetRaw(key)` | Get raw JSON value | -| `Delete(key)` | Remove key | -| `Has(key)` | Check if key exists | -| `Keys(prefix)` | List keys (optionally by prefix) | -| `List(prefix)` | List keys with metadata | -| `Clear(prefix)` | Delete keys (optionally by prefix) | - -#### Generic Helper Functions (Go 1.18+) - -The SDK provides type-safe generic functions for cleaner KV operations: - -```go -// Type-safe get - returns value directly instead of requiring dest pointer -version, err := agentfs.KVGet[string](ctx, afs.KV, "config:version") -count, err := agentfs.KVGet[int](ctx, afs.KV, "stats:count") - -type Config struct { - Debug bool `json:"debug"` -} -cfg, err := agentfs.KVGet[Config](ctx, afs.KV, "app:config") - -// Get with default value - returns default if key doesn't exist -debug, err := agentfs.KVGetOrDefault(ctx, afs.KV, "config:debug", false) -name, err := agentfs.KVGetOrDefault(ctx, afs.KV, "user:name", "anonymous") - -// Get with zero value - returns zero value if key doesn't exist -count, err := agentfs.KVGetOrZero[int](ctx, afs.KV, "stats:count") // 0 if not found -name, err := agentfs.KVGetOrZero[string](ctx, afs.KV, "user:name") // "" if not found - -// Type-safe set (for API consistency) -err := agentfs.KVSet(ctx, afs.KV, "config:version", "1.0.0") -``` - -| Function | Description | -|--------------------------------------------|-----------------------------------------| -| `KVGet[T](ctx, kv, key)` | Type-safe get, returns value directly | -| `KVGetOrDefault[T](ctx, kv, key, default)` | Returns default if key not found | -| `KVGetOrZero[T](ctx, kv, key)` | Returns zero value if key not found | -| `KVSet[T](ctx, kv, key, value)` | Type-safe set (wrapper for consistency) | - -### Tool Calls - -| Method | Description | -|-------------------------------|---------------------------| -| `Start(name, params)` | Begin tracking a call | -| `PendingCall.Success(result)` | Mark as successful | -| `PendingCall.Error(err)` | Mark as failed | -| `Record(...)` | Insert complete record | -| `Get(id)` | Get call by ID | -| `GetByName(name, limit)` | Get calls by name | -| `GetRecent(since, limit)` | Get recent calls | -| `GetStats()` | Get aggregated statistics | - -## Error Handling - -The SDK uses POSIX-style error codes: - -```go -import "errors" - -_, err := afs.FS.Stat(ctx, "/missing") -if agentfs.IsNotExist(err) { - // Handle missing file -} - -var fsErr *agentfs.FSError -if errors.As(err, &fsErr) { - fmt.Printf("Code: %d, Syscall: %s, Path: %s\n", - fsErr.Code, fsErr.Syscall, fsErr.Path) -} -``` - -Common error codes: -- `ENOENT` (2) - No such file or directory -- `EEXIST` (17) - File exists -- `ENOTDIR` (20) - Not a directory -- `EISDIR` (21) - Is a directory -- `ENOTEMPTY` (39) - Directory not empty - -## Interfaces for Testing - -The SDK provides optional interfaces for users who want to mock the filesystem, KV store, or tool calls in their tests: - -```go -// Your application code - program against the interface -type MyService struct { - fs agentfs.FileSystem // Interface, not *Filesystem -} - -func NewMyService(afs *agentfs.AgentFS) *MyService { - return &MyService{fs: afs.FS} // Concrete type satisfies interface -} - -// In tests - use a mock -type MockFS struct{} -func (m *MockFS) Stat(ctx context.Context, path string) (*agentfs.Stats, error) { - return &agentfs.Stats{Size: 100}, nil -} -// ... implement other methods - -svc := &MyService{fs: &MockFS{}} -``` - -| Interface | Concrete Type | -|----------------------|---------------| -| `FileSystem` | `*Filesystem` | -| `KVStoreInterface` | `*KVStore` | -| `ToolCallsInterface` | `*ToolCalls` | - -These interfaces are entirely optional. The SDK continues to return concrete types, and users who don't need mocking can ignore the interfaces entirely. - -## Go Standard Library `io/fs` Integration - -The SDK provides an `io/fs` compatible wrapper for use with Go standard library functions: - -```go -import ( - "io/fs" - "html/template" - "net/http" -) - -// Create io/fs wrapper -iofs := agentfs.NewIOFS(afs.FS) - -// Use with fs.WalkDir -fs.WalkDir(iofs, ".", func(path string, d fs.DirEntry, err error) error { - fmt.Println(path) - return nil -}) - -// Use with fs.Glob -matches, _ := fs.Glob(iofs, "*.txt") - -// Use with template.ParseFS -tmpl, _ := template.ParseFS(iofs, "templates/*.html") - -// Use with http.FileServer -http.Handle("/", http.FileServer(http.FS(iofs))) - -// Use SubFS for subdirectories -subFS, _ := iofs.Sub("public") -http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(subFS)))) - -// Use with context for timeout control -ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) -defer cancel() -iofsWithCtx := iofs.WithContext(ctx) -``` - -### Supported Interfaces - -| Interface | Status | -|------------------|-----------------------------| -| `fs.FS` | Supported | -| `fs.StatFS` | Supported | -| `fs.ReadFileFS` | Supported | -| `fs.ReadDirFS` | Supported | -| `fs.SubFS` | Supported | -| `fs.ReadDirFile` | Supported (for directories) | - -**Note:** Use `fs.Glob(iofs, pattern)` for glob matching - it uses the `ReadDir` implementation. - -## Benchmarks - -Run benchmarks with: - -```bash -go test -bench=. -run=^$ -benchmem -``` - -### Sequential I/O (1MB files) - -| Operation | Throughput | Allocs/op | -|-----------------|------------|-----------| -| WriteFile | 274 MB/s | 1,898 | -| ReadFile | 752 MB/s | 1,101 | -| Streaming Write | 93 MB/s | 13,628 | -| Streaming Read | 167 MB/s | 12,612 | - -### Random I/O (16MB file, 4KB operations) - -| Operation | Throughput | -|-------------|------------| -| RandomRead | 138 MB/s | -| RandomWrite | 56 MB/s | - -### Metadata Operations - -| Operation | Latency | -|-----------------------|---------| -| Stat | 13 μs | -| Readdir (100 entries) | 57 μs | -| Mkdir | 60 μs | -| Rename | 24 μs | -| Create+Delete | 132 μs | - -### Path Resolution - -| Depth | Latency | -|-----------|---------| -| 1 level | 18 μs | -| 10 levels | 60 μs | -| 20 levels | 106 μs | - -### Chunk Size Impact (1MB file) - -| Chunk Size | Write | Read | -|---------------|----------|----------| -| 4KB (default) | 266 MB/s | 740 MB/s | -| 16KB | 491 MB/s | 644 MB/s | -| 64KB | 947 MB/s | 842 MB/s | - -### KV Store - -| Operation | Latency | -|-------------------|---------| -| Get (small value) | 4 μs | -| Set (small value) | 16 μs | -| Has | 3 μs | -| Keys (100 keys) | 56 μs | - -### Key Insights - -- **Chunk size matters**: Larger chunks significantly improve write performance (3.5x from 4KB to 64KB). Consider using `ChunkSize: 65536` for write-heavy workloads. -- **Bulk reads are fast**: ReadFile outperforms streaming for whole-file reads. -- **KV is efficient**: Small value operations complete in single-digit microseconds. - -*Benchmarks run on Apple M1 Ultra. Results vary by hardware and SQLite configuration.* - -## Connection Pool - -The SDK uses Go's `database/sql` connection pool. You can tune it for your workload: - -```go -afs, err := agentfs.Open(ctx, agentfs.AgentFSOptions{ - ID: "my-agent", - Pool: agentfs.PoolOptions{ - MaxOpenConns: 1, // Serialize access (recommended for SQLite) - MaxIdleConns: 1, // Keep one connection ready - ConnMaxLifetime: 0, // Connections never expire - ConnMaxIdleTime: 5 * time.Minute, // Close idle connections after 5m - }, -}) -``` - -**Recommendations:** -- **Single-process**: `MaxOpenConns: 1` ensures serialized access, avoiding SQLite lock contention -- **Read-heavy**: Higher `MaxOpenConns` can improve read parallelism with WAL mode -- **Long-running**: Set `ConnMaxIdleTime` to periodically refresh connections - -## Schema Compatibility - -This SDK implements the AgentFS specification v0.4 and is compatible with databases created by: -- Python SDK (agentfs-py) -- TypeScript SDK (agentfs-ts) -- Rust SDK (agentfs-rs) - -## License - -See the main AgentFS repository for license information. diff --git a/sdk/go/SPEC_TESTS.md b/sdk/go/SPEC_TESTS.md deleted file mode 100644 index 3723f0ec..00000000 --- a/sdk/go/SPEC_TESTS.md +++ /dev/null @@ -1,413 +0,0 @@ -# AgentFS Specification Testable Elements - -This document captures the testable elements from the AgentFS Specification (SPEC.md) -and defines the testing strategy for ensuring spec compliance. - -**Current Spec Version:** 0.4 - ---- - -## Testing Strategy - -### Approach: Dedicated Spec Compliance Suite - -We maintain a dedicated `spec_test.go` focused exclusively on spec compliance, separate -from existing implementation tests. - -| Approach | Pros | Cons | -|----------|------|------| -| **Dedicated suite** | Clear traceability, easy auditing, spec changes are isolated | Some duplication | -| **Interspersed** | No duplication | Hard to audit, spec mapping scattered, harder to maintain | - -### File Organization - -``` -sdk/go/ -├── spec_test.go # Spec compliance tests -├── agentfs_test.go # Implementation/behavior tests -├── overlay_test.go # Overlay implementation tests -├── symlink_test.go # Symlink resolution tests -└── SPEC_TESTS.md # Spec element tracking (this file) -``` - -### Design Principles for `spec_test.go` - -1. **Test names include spec IDs** for traceability: - ```go - func TestSpec_FS24_RootInodeMustExist(t *testing.T) - func TestSpec_OV18_WhiteoutRemovedOnCreate(t *testing.T) - func TestSpec_KV13_KeysMustBeUnique(t *testing.T) - ``` - -2. **Organized by spec section** (Tool Calls, Virtual Filesystem, Overlay, Key-Value) - -3. **Minimal tests** - just enough to verify compliance, not exhaustive edge cases - -4. **Schema tests verify structure** by querying sqlite_master - -5. **Version-tagged** for tracking which spec version introduced each element - -### Test File Responsibilities - -| Test File | Purpose | Scope | -|-----------|---------|-------| -| `spec_test.go` | Spec compliance | Minimal verification of each spec element | -| `agentfs_test.go` | Implementation | Detailed behavior, edge cases, integration | -| `overlay_test.go` | Overlay behavior | Copy-up, caching, complex scenarios | -| `symlink_test.go` | Symlink resolution | Follow, ELOOP, intermediate traversal | - -### Running Spec Tests - -```bash -# Run only spec compliance tests -go test -v -run TestSpec ./... - -# Run specific section -go test -v -run TestSpec_FS ./... # Filesystem -go test -v -run TestSpec_OV ./... # Overlay -go test -v -run TestSpec_KV ./... # Key-Value -go test -v -run TestSpec_TC ./... # Tool Calls -``` - -### Workflow for Spec Changes - -``` -1. Spec updated (SPEC.md) - ↓ -2. Update SPEC_TESTS.md with new elements (this file) - ↓ -3. Add tests to spec_test.go with spec IDs - ↓ -4. Update Status column in SPEC_TESTS.md to ✓ -``` - -### Status Legend - -| Status | Meaning | -|--------|---------| -| ✓ | Test exists in spec_test.go | -| ~ | Covered by implementation tests (not spec_test.go) | -| (empty) | Not yet tested | - ---- - -## 1. Tool Calls - -### Schema Constraints - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| TC-1 | Table `tool_calls` has all required columns with correct types | 0.0 | | -| TC-2 | Index `idx_tool_calls_name` exists on `name` column | 0.0 | | -| TC-3 | Index `idx_tool_calls_started_at` exists on `started_at` column | 0.0 | | -| TC-4 | `id` is auto-incrementing primary key | 0.0 | | - -### Consistency Rules - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| TC-5 | Exactly one of `result` or `error` SHOULD be non-NULL (mutual exclusion) | 0.0 | | -| TC-6 | `completed_at` MUST always be set (no NULL values) | 0.0 | | -| TC-7 | `duration_ms` MUST always be set and equal to `(completed_at - started_at) * 1000` | 0.0 | | -| TC-8 | `parameters` MUST be valid JSON string when present | 0.0 | | -| TC-9 | `result` MUST be valid JSON string when present | 0.0 | | -| TC-10 | Records MUST NOT be updated after insertion (insert-only audit log) | 0.0 | | -| TC-11 | Records MUST NOT be deleted (insert-only audit log) | 0.0 | | - -### Operations - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| TC-12 | Record tool call inserts correctly with all fields | 0.0 | | -| TC-13 | Query by name returns correct records ordered by `started_at DESC` | 0.0 | | -| TC-14 | Query recent tool calls filters by `started_at` correctly | 0.0 | | -| TC-15 | Performance analysis query groups by name with correct counts | 0.0 | | - ---- - -## 2. Virtual Filesystem - -### Schema Constraints - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-1 | Table `fs_config` exists with `key` TEXT PRIMARY KEY, `value` TEXT NOT NULL | 0.1 | | -| FS-2 | Table `fs_inode` has columns: ino, mode, nlink, uid, gid, size, atime, mtime, ctime | 0.0 | | -| FS-3 | Table `fs_inode` has `nlink` column for link count | 0.2 | | -| FS-4 | Table `fs_inode` has `rdev` column for device numbers | 0.4 | | -| FS-5 | Table `fs_inode` has nanosecond columns: atime_nsec, mtime_nsec, ctime_nsec | 0.4 | | -| FS-6 | Table `fs_dentry` has columns (id, name, parent_ino, ino) with UNIQUE(parent_ino, name) | 0.0 | | -| FS-7 | Index `idx_fs_dentry_parent` exists on (parent_ino, name) | 0.0 | | -| FS-8 | Table `fs_data` has PRIMARY KEY (ino, chunk_index) | 0.1 | | -| FS-9 | Table `fs_symlink` has `ino` INTEGER PRIMARY KEY, `target` TEXT NOT NULL | 0.0 | | - -### Configuration - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-10 | `chunk_size` configuration exists with default value 4096 | 0.1 | | -| FS-11 | Configuration is immutable after initialization | 0.1 | | - -### Mode Encoding - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-12 | S_IFMT (0o170000) correctly masks file type | 0.0 | | -| FS-13 | S_IFREG (0o100000) identifies regular files | 0.0 | | -| FS-14 | S_IFDIR (0o040000) identifies directories | 0.0 | | -| FS-15 | S_IFLNK (0o120000) identifies symbolic links | 0.0 | | -| FS-16 | S_IFIFO (0o010000) identifies FIFOs/named pipes | 0.4 | | -| FS-17 | S_IFCHR (0o020000) identifies character devices | 0.4 | | -| FS-18 | S_IFBLK (0o060000) identifies block devices | 0.4 | | -| FS-19 | S_IFSOCK (0o140000) identifies sockets | 0.4 | | -| FS-20 | Permission bits (0o000777) correctly encode rwxrwxrwx | 0.0 | | - -### Initialization - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-21 | Root inode (ino=1) is created on initialization | 0.0 | | -| FS-22 | Root inode has mode 0o040755 (directory with rwxr-xr-x) | 0.0 | | -| FS-23 | Root inode has nlink=1 | 0.2 | | - -### Consistency Rules - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-24 | Root inode (ino=1) MUST always exist | 0.0 | | -| FS-25 | Every dentry MUST reference a valid inode | 0.0 | | -| FS-26 | Every dentry MUST reference a valid parent inode | 0.0 | | -| FS-27 | No directory MAY contain duplicate names | 0.0 | | -| FS-28 | Directories MUST have mode with S_IFDIR bit set | 0.0 | | -| FS-29 | Regular files MUST have mode with S_IFREG bit set | 0.0 | | -| FS-30 | File size MUST match total size of all data chunks | 0.0 | | -| FS-31 | Every inode MUST have at least one dentry (except root) | 0.0 | | - -### Operations - Path Resolution - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-32 | Path resolution starts at root inode (ino=1) | 0.0 | | -| FS-33 | Path components are split by `/` with empty components filtered | 0.0 | | -| FS-34 | Each component lookup uses parent_ino and name | 0.0 | | -| FS-35 | Missing component returns NULL/not found | 0.0 | | - -### Operations - File Creation - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-36 | Creating file resolves parent directory first | 0.0 | | -| FS-37 | New inode is inserted with correct mode, uid, gid, timestamps | 0.0 | | -| FS-38 | Directory entry is created linking name to inode | 0.0 | | -| FS-39 | Link count is incremented on inode | 0.2 | | -| FS-40 | Data is split into chunks of chunk_size | 0.1 | | -| FS-41 | Inode size is updated to match content | 0.0 | | - -### Operations - File Reading - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-42 | Reading file resolves path to inode | 0.0 | | -| FS-43 | All chunks are fetched in order by chunk_index ASC | 0.1 | | -| FS-44 | Chunks are concatenated correctly | 0.1 | | -| FS-45 | Access time (atime) is updated on read | 0.0 | | - -### Operations - Partial File Reading - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-46 | start_chunk = offset / chunk_size | 0.1 | | -| FS-47 | end_chunk = (offset + length - 1) / chunk_size | 0.1 | | -| FS-48 | Only required chunks are fetched | 0.1 | | -| FS-49 | offset_in_first_chunk = offset % chunk_size | 0.1 | | -| FS-50 | Correct byte range is extracted | 0.1 | | - -### Operations - Directory Listing - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-51 | Readdir returns names from fs_dentry for parent_ino | 0.0 | | -| FS-52 | Results are ordered by name ASC | 0.0 | | - -### Operations - File Deletion - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-53 | Deleting file removes directory entry | 0.0 | | -| FS-54 | Link count is decremented | 0.2 | | -| FS-55 | When nlink reaches 0, inode is deleted | 0.2 | | -| FS-56 | When nlink reaches 0, data chunks are deleted | 0.0 | | - -### Operations - Hard Links - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-57 | Hard link creates new dentry pointing to same inode | 0.0 | | -| FS-58 | Link count is incremented on target inode | 0.2 | | -| FS-59 | Multiple dentries can point to same inode | 0.0 | | - -### Operations - Stat - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-60 | Stat returns all inode fields (ino, mode, nlink, uid, gid, size, atime, mtime, ctime, rdev) | 0.4 | | -| FS-61 | Stat returns nanosecond fields (atime_nsec, mtime_nsec, ctime_nsec) | 0.4 | | - -### Chunk Storage - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-62 | Directories MUST NOT have data chunks | 0.0 | | -| FS-63 | All chunks except last MUST be exactly chunk_size bytes | 0.1 | | -| FS-64 | Last chunk MAY be smaller than chunk_size | 0.1 | | -| FS-65 | Byte offset = chunk_index * chunk_size | 0.1 | | -| FS-66 | Empty files have inode but no data chunks | 0.0 | | - -### Symlinks - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| FS-67 | Symlink inode is stored in fs_symlink table | 0.0 | | -| FS-68 | Symlink target can be absolute or relative path | 0.0 | | -| FS-69 | Symlink has mode with S_IFLNK bit set | 0.0 | | - ---- - -## 3. Overlay Filesystem - -### Schema Constraints - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| OV-1 | Table `fs_whiteout` has `path` TEXT PRIMARY KEY | 0.2 | | -| OV-2 | Table `fs_whiteout` has `parent_path` TEXT NOT NULL | 0.2 | | -| OV-3 | Table `fs_whiteout` has `created_at` INTEGER NOT NULL | 0.2 | | -| OV-4 | Index `idx_fs_whiteout_parent` exists on `parent_path` | 0.2 | | -| OV-5 | Table `fs_origin` has `delta_ino` INTEGER PRIMARY KEY | 0.3 | | -| OV-6 | Table `fs_origin` has `base_ino` INTEGER NOT NULL | 0.3 | | - -### Whiteout Operations - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| OV-7 | Create whiteout inserts path with parent_path and created_at | 0.2 | | -| OV-8 | Check whiteout returns true for existing whiteout path | 0.2 | | -| OV-9 | Remove whiteout deletes the path entry | 0.2 | | -| OV-10 | List child whiteouts returns paths with matching parent_path | 0.2 | | - -### Overlay Lookup Semantics - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| OV-11 | Lookup returns delta entry if path exists in delta | 0.2 | | -| OV-12 | Lookup returns "not found" if path has whiteout | 0.2 | | -| OV-13 | Lookup returns base entry if path exists only in base | 0.2 | | -| OV-14 | Lookup returns "not found" if path doesn't exist in either layer | 0.2 | | - -### Origin Tracking Operations - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| OV-15 | Store origin mapping inserts delta_ino → base_ino | 0.3 | | -| OV-16 | Get origin returns base_ino for delta_ino if mapping exists | 0.3 | | -| OV-17 | Origin mapping uses INSERT OR REPLACE semantics | 0.3 | | - -### Consistency Rules - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| OV-18 | Whiteout MUST be removed when new file created at that path | 0.2 | | -| OV-19 | Whiteout MUST be created when deleting file from base layer | 0.2 | | -| OV-20 | parent_path MUST be correctly derived from path | 0.2 | | -| OV-21 | Whiteouts only affect overlay lookups, not base filesystem | 0.2 | | -| OV-22 | Origin mapping MUST be stored when copying from base to delta | 0.3 | | -| OV-23 | Base inode MUST be returned when stat'ing delta file with origin | 0.3 | | - -### Parent Path Derivation - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| OV-24 | Root directory `/` has parent_path `/` | 0.2 | | -| OV-25 | Path `/foo/bar` has parent_path `/foo` | 0.2 | | -| OV-26 | Path `/foo` has parent_path `/` | 0.2 | | - ---- - -## 4. Key-Value Store - -### Schema Constraints - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| KV-1 | Table `kv_store` has `key` TEXT PRIMARY KEY | 0.0 | | -| KV-2 | Table `kv_store` has `value` TEXT NOT NULL | 0.0 | | -| KV-3 | Table `kv_store` has `created_at` with DEFAULT (unixepoch()) | 0.0 | | -| KV-4 | Table `kv_store` has `updated_at` with DEFAULT (unixepoch()) | 0.0 | | -| KV-5 | Index `idx_kv_store_created_at` exists on `created_at` | 0.0 | | - -### Operations - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| KV-6 | Set inserts new key with value, created_at, updated_at | 0.0 | | -| KV-7 | Set updates existing key's value and updated_at (upsert) | 0.0 | | -| KV-8 | Set preserves created_at on update | 0.0 | | -| KV-9 | Get returns value for existing key | 0.0 | | -| KV-10 | Get returns NULL/not found for missing key | 0.0 | | -| KV-11 | Delete removes key | 0.0 | | -| KV-12 | List returns all keys ordered by key ASC | 0.0 | | - -### Consistency Rules - -| ID | Testable Element | Version | Status | -|----|------------------|---------|--------| -| KV-13 | Keys MUST be unique | 0.0 | | -| KV-14 | Values MUST be valid JSON strings | 0.0 | | -| KV-15 | Timestamps MUST use Unix epoch format (seconds) | 0.0 | | - ---- - -## Summary - -| Section | Count | Versions | -|---------|-------|----------| -| Tool Calls | 15 | 0.0 | -| Virtual Filesystem | 69 | 0.0, 0.1, 0.2, 0.4 | -| Overlay Filesystem | 26 | 0.2, 0.3 | -| Key-Value Store | 15 | 0.0 | -| **Total** | **125** | | - ---- - -## Version History - -| Version | Changes | -|---------|---------| -| 0.4 | Nanosecond timestamp precision (atime_nsec, mtime_nsec, ctime_nsec), POSIX special files (rdev, S_IFIFO, S_IFCHR, S_IFBLK, S_IFSOCK) | -| 0.3 | fs_origin table for overlay copy-up origin tracking | -| 0.2 | fs_whiteout table with parent_path, nlink column in fs_inode | -| 0.1 | fs_config table, chunk-based storage with chunk_index | -| 0.0 | Initial specification (tool_calls, fs_inode, fs_dentry, fs_data, fs_symlink, kv_store) | - ---- - -## Test Coverage Mapping - -Tests for these elements can be found in: - -- `agentfs_test.go` - Core filesystem operations -- `overlay_test.go` - Overlay filesystem operations -- `symlink_test.go` - Symlink resolution behavior - -### Overlay Consistency Rules (OV-18 to OV-23) - -These critical consistency rules have dedicated tests: - -| Rule | Test Function | -|------|---------------| -| OV-18 | `TestOverlay_ConsistencyRule1_WhiteoutRemovedOnCreate` | -| OV-19 | `TestOverlay_ConsistencyRule2_WhiteoutCreatedOnDelete` | -| OV-20 | `TestOverlay_ConsistencyRule3_ParentPathCorrectlyDerived` | -| OV-21 | `TestOverlay_ConsistencyRule4_WhiteoutsAffectOverlayLookups` | -| OV-22 | `TestOverlay_ConsistencyRule5_OriginMappingStored` | -| OV-23 | `TestOverlay_ConsistencyRule6_BaseInodeReturnedForCopiedUp` | diff --git a/sdk/go/agentfs.go b/sdk/go/agentfs.go deleted file mode 100644 index 8470ab6c..00000000 --- a/sdk/go/agentfs.go +++ /dev/null @@ -1,266 +0,0 @@ -// Package agentfs provides a SQLite-backed virtual filesystem, key-value store, -// and tool call tracking for AI agents. -package agentfs - -import ( - "context" - "database/sql" - "fmt" - "os" - "path/filepath" - "regexp" - "strconv" - - _ "modernc.org/sqlite" -) - -// AgentFS is the main entry point providing access to filesystem, -// key-value store, and tool call tracking. -type AgentFS struct { - db *sql.DB - ownsDB bool // true if we opened the DB and should close it - path string - - // FS provides filesystem operations - FS *Filesystem - - // KV provides key-value store operations - KV *KVStore - - // Tools provides tool call tracking operations - Tools *ToolCalls -} - -// ErrSchemaVersionMismatch is returned when a database was created with an -// incompatible schema version. -type ErrSchemaVersionMismatch struct { - Found string - Expected string -} - -func (e *ErrSchemaVersionMismatch) Error() string { - return fmt.Sprintf("schema version mismatch: found %q, expected %q", e.Found, e.Expected) -} - -// validIDPattern matches valid agent IDs (alphanumeric, hyphens, underscores) -var validIDPattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`) - -// Open creates or opens an AgentFS database. -// -// If opts.Path is provided, it is used directly as the database path. -// If opts.ID is provided (without Path), the database is stored at ~/.agentfs/{id}.db -// At least one of Path or ID must be provided. -func Open(ctx context.Context, opts AgentFSOptions) (*AgentFS, error) { - dbPath, err := resolveDBPath(opts) - if err != nil { - return nil, err - } - - // Ensure parent directory exists - dir := filepath.Dir(dbPath) - if err := os.MkdirAll(dir, 0755); err != nil { - return nil, fmt.Errorf("failed to create directory %s: %w", dir, err) - } - - // Open database - db, err := sql.Open("sqlite", dbPath) - if err != nil { - return nil, fmt.Errorf("failed to open database: %w", err) - } - - // Configure connection pool - if opts.Pool.MaxOpenConns > 0 { - db.SetMaxOpenConns(opts.Pool.MaxOpenConns) - } - if opts.Pool.MaxIdleConns > 0 { - db.SetMaxIdleConns(opts.Pool.MaxIdleConns) - } - if opts.Pool.ConnMaxLifetime > 0 { - db.SetConnMaxLifetime(opts.Pool.ConnMaxLifetime) - } - if opts.Pool.ConnMaxIdleTime > 0 { - db.SetConnMaxIdleTime(opts.Pool.ConnMaxIdleTime) - } - - // Enable WAL mode for better concurrency - if _, err := db.ExecContext(ctx, "PRAGMA journal_mode=WAL"); err != nil { - db.Close() - return nil, fmt.Errorf("failed to enable WAL mode: %w", err) - } - - // Enable foreign keys - if _, err := db.ExecContext(ctx, "PRAGMA foreign_keys=ON"); err != nil { - db.Close() - return nil, fmt.Errorf("failed to enable foreign keys: %w", err) - } - - afs, err := initAgentFS(ctx, db, dbPath, true, opts) - if err != nil { - db.Close() - return nil, err - } - - return afs, nil -} - -// OpenWith creates an AgentFS using an existing *sql.DB connection. -// -// The caller retains ownership of the database connection. Calling Close() -// on the returned AgentFS will not close the underlying database. -// -// OpenWithOptions can be used to configure cache and chunk size. Pool options -// are ignored since the connection is externally managed. -func OpenWith(ctx context.Context, db *sql.DB, opts ...OpenWithOption) (*AgentFS, error) { - o := openWithOptions{} - for _, opt := range opts { - opt(&o) - } - - afsOpts := AgentFSOptions{ - ChunkSize: o.chunkSize, - } - - afs, err := initAgentFS(ctx, db, "", false, afsOpts) - if err != nil { - return nil, err - } - - return afs, nil -} - -// OpenWithOption configures optional settings for OpenWith. -type OpenWithOption func(*openWithOptions) - -type openWithOptions struct { - chunkSize int -} - -// WithChunkSize sets the chunk size for file data storage. -func WithChunkSize(size int) OpenWithOption { - return func(o *openWithOptions) { - o.chunkSize = size - } -} - -// initAgentFS contains the shared initialization logic for Open and OpenWith. -func initAgentFS(ctx context.Context, db *sql.DB, dbPath string, ownsDB bool, opts AgentFSOptions) (*AgentFS, error) { - // Initialize schema - if err := initSchema(ctx, db); err != nil { - return nil, fmt.Errorf("failed to initialize schema: %w", err) - } - - // Apply nanosecond timestamp migrations (backwards-compatible) - // These add columns if they don't exist; errors are ignored for existing columns - for _, stmt := range nsecMigrations() { - db.ExecContext(ctx, stmt) // Ignore errors (column may already exist) - } - - // Initialize and validate schema version - if _, err := db.ExecContext(ctx, initSchemaVersion, schemaVersion); err != nil { - return nil, fmt.Errorf("failed to initialize schema_version: %w", err) - } - - var foundVersion string - if err := db.QueryRowContext(ctx, getSchemaVersion).Scan(&foundVersion); err != nil { - return nil, fmt.Errorf("failed to read schema_version: %w", err) - } - if foundVersion != schemaVersion { - return nil, &ErrSchemaVersionMismatch{Found: foundVersion, Expected: schemaVersion} - } - - // Determine chunk size - chunkSize := opts.ChunkSize - if chunkSize <= 0 { - chunkSize = DefaultChunkSize - } - - // Initialize filesystem config (chunk_size) - if _, err := db.ExecContext(ctx, initFsConfig, strconv.Itoa(chunkSize)); err != nil { - return nil, fmt.Errorf("failed to initialize fs_config: %w", err) - } - - // Initialize root inode - if _, err := db.ExecContext(ctx, initRootInode, DefaultDirMode); err != nil { - return nil, fmt.Errorf("failed to initialize root inode: %w", err) - } - - // Read actual chunk size from database (may differ if database already existed) - var chunkSizeStr string - if err := db.QueryRowContext(ctx, getChunkSize).Scan(&chunkSizeStr); err != nil { - return nil, fmt.Errorf("failed to read chunk_size: %w", err) - } - actualChunkSize, err := strconv.Atoi(chunkSizeStr) - if err != nil { - return nil, fmt.Errorf("invalid chunk_size value: %w", err) - } - - afs := &AgentFS{ - db: db, - ownsDB: ownsDB, - path: dbPath, - } - - // Initialize subsystems - afs.FS = &Filesystem{ - db: db, - chunkSize: actualChunkSize, - } - afs.KV = &KVStore{db: db} - afs.Tools = &ToolCalls{db: db} - - return afs, nil -} - -// Close closes the AgentFS instance. -// If the database was opened by Open, the connection is closed. -// If the database was provided via OpenWith, the connection is not closed. -func (a *AgentFS) Close() error { - if a.ownsDB { - return a.db.Close() - } - return nil -} - -// Path returns the path to the underlying database file. -// Returns an empty string if the AgentFS was created via OpenWith. -func (a *AgentFS) Path() string { - return a.path -} - -// DB returns the underlying *sql.DB connection. -func (a *AgentFS) DB() *sql.DB { - return a.db -} - -// resolveDBPath determines the database file path from options -func resolveDBPath(opts AgentFSOptions) (string, error) { - if opts.Path != "" { - return opts.Path, nil - } - - if opts.ID == "" { - return "", fmt.Errorf("either Path or ID must be provided") - } - - if !validIDPattern.MatchString(opts.ID) { - return "", fmt.Errorf("invalid agent ID: must match pattern %s", validIDPattern.String()) - } - - // Default to ~/.agentfs/{id}.db - home, err := os.UserHomeDir() - if err != nil { - return "", fmt.Errorf("failed to get home directory: %w", err) - } - - return filepath.Join(home, ".agentfs", opts.ID+".db"), nil -} - -// initSchema creates all tables and indexes if they don't exist -func initSchema(ctx context.Context, db *sql.DB) error { - for _, stmt := range allSchemaStatements() { - if _, err := db.ExecContext(ctx, stmt); err != nil { - return fmt.Errorf("schema creation failed: %w", err) - } - } - return nil -} diff --git a/sdk/go/agentfs_test.go b/sdk/go/agentfs_test.go deleted file mode 100644 index 52bf0a6f..00000000 --- a/sdk/go/agentfs_test.go +++ /dev/null @@ -1,2423 +0,0 @@ -package agentfs - -import ( - "context" - "database/sql" - "errors" - "os" - "path/filepath" - "strings" - "testing" - "time" - - _ "modernc.org/sqlite" -) - -func TestOpen(t *testing.T) { - ctx := context.Background() - - t.Run("with path", func(t *testing.T) { - tmpDir := t.TempDir() - dbPath := filepath.Join(tmpDir, "test.db") - - afs, err := Open(ctx, AgentFSOptions{Path: dbPath}) - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer afs.Close() - - if afs.Path() != dbPath { - t.Errorf("Path() = %q, want %q", afs.Path(), dbPath) - } - - // Verify database file was created - if _, err := os.Stat(dbPath); os.IsNotExist(err) { - t.Error("Database file was not created") - } - }) - - t.Run("with ID", func(t *testing.T) { - // Create a temp home directory - tmpHome := t.TempDir() - oldHome := os.Getenv("HOME") - os.Setenv("HOME", tmpHome) - defer os.Setenv("HOME", oldHome) - - afs, err := Open(ctx, AgentFSOptions{ID: "test-agent"}) - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer afs.Close() - - expectedPath := filepath.Join(tmpHome, ".agentfs", "test-agent.db") - if afs.Path() != expectedPath { - t.Errorf("Path() = %q, want %q", afs.Path(), expectedPath) - } - }) - - t.Run("invalid ID", func(t *testing.T) { - _, err := Open(ctx, AgentFSOptions{ID: "invalid id with spaces"}) - if err == nil { - t.Error("Expected error for invalid ID") - } - }) - - t.Run("no ID or path", func(t *testing.T) { - _, err := Open(ctx, AgentFSOptions{}) - if err == nil { - t.Error("Expected error when neither ID nor Path provided") - } - }) -} - -func TestFilesystem(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - t.Run("root directory exists", func(t *testing.T) { - stats, err := fs.Stat(ctx, "/") - if err != nil { - t.Fatalf("Stat(/) failed: %v", err) - } - if !stats.IsDir() { - t.Error("Root is not a directory") - } - if stats.Ino != RootIno { - t.Errorf("Root ino = %d, want %d", stats.Ino, RootIno) - } - }) - - t.Run("mkdir and stat", func(t *testing.T) { - err := fs.Mkdir(ctx, "/testdir", 0o755) - if err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - - stats, err := fs.Stat(ctx, "/testdir") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !stats.IsDir() { - t.Error("testdir is not a directory") - } - if stats.Permissions() != 0o755 { - t.Errorf("Permissions = %o, want %o", stats.Permissions(), 0o755) - } - }) - - t.Run("mkdir existing", func(t *testing.T) { - err := fs.Mkdir(ctx, "/testdir", 0o755) - if !IsExist(err) { - t.Errorf("Expected EEXIST, got %v", err) - } - }) - - t.Run("mkdirAll", func(t *testing.T) { - err := fs.MkdirAll(ctx, "/a/b/c/d", 0o755) - if err != nil { - t.Fatalf("MkdirAll failed: %v", err) - } - - stats, err := fs.Stat(ctx, "/a/b/c/d") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !stats.IsDir() { - t.Error("Not a directory") - } - }) - - t.Run("writeFile and readFile", func(t *testing.T) { - content := []byte("Hello, World!") - err := fs.WriteFile(ctx, "/hello.txt", content, 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - data, err := fs.ReadFile(ctx, "/hello.txt") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - if string(data) != string(content) { - t.Errorf("ReadFile = %q, want %q", data, content) - } - - stats, err := fs.Stat(ctx, "/hello.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !stats.IsRegularFile() { - t.Error("Not a regular file") - } - if stats.Size != int64(len(content)) { - t.Errorf("Size = %d, want %d", stats.Size, len(content)) - } - }) - - t.Run("writeFile creates parent directories", func(t *testing.T) { - err := fs.WriteFile(ctx, "/new/path/to/file.txt", []byte("test"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - _, err = fs.Stat(ctx, "/new/path/to") - if err != nil { - t.Errorf("Parent directory not created: %v", err) - } - }) - - t.Run("readdir", func(t *testing.T) { - names, err := fs.Readdir(ctx, "/") - if err != nil { - t.Fatalf("Readdir failed: %v", err) - } - - // Should contain at least testdir, hello.txt, a, new - found := make(map[string]bool) - for _, name := range names { - found[name] = true - } - - expected := []string{"testdir", "hello.txt", "a", "new"} - for _, name := range expected { - if !found[name] { - t.Errorf("Expected %q in directory listing", name) - } - } - }) - - t.Run("readdirPlus", func(t *testing.T) { - entries, err := fs.ReaddirPlus(ctx, "/") - if err != nil { - t.Fatalf("ReaddirPlus failed: %v", err) - } - - for _, entry := range entries { - if entry.Stats == nil { - t.Errorf("Entry %q has nil stats", entry.Name) - } - } - }) - - t.Run("unlink", func(t *testing.T) { - err := fs.WriteFile(ctx, "/todelete.txt", []byte("delete me"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - err = fs.Unlink(ctx, "/todelete.txt") - if err != nil { - t.Fatalf("Unlink failed: %v", err) - } - - _, err = fs.Stat(ctx, "/todelete.txt") - if !IsNotExist(err) { - t.Errorf("Expected ENOENT after unlink, got %v", err) - } - }) - - t.Run("unlink directory fails", func(t *testing.T) { - err := fs.Mkdir(ctx, "/cantunlink", 0o755) - if err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - - err = fs.Unlink(ctx, "/cantunlink") - var fsErr *FSError - if !errors.As(err, &fsErr) || fsErr.Code != EISDIR { - t.Errorf("Expected EISDIR, got %v", err) - } - }) - - t.Run("rmdir", func(t *testing.T) { - err := fs.Mkdir(ctx, "/emptydir", 0o755) - if err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - - err = fs.Rmdir(ctx, "/emptydir") - if err != nil { - t.Fatalf("Rmdir failed: %v", err) - } - - _, err = fs.Stat(ctx, "/emptydir") - if !IsNotExist(err) { - t.Errorf("Expected ENOENT after rmdir, got %v", err) - } - }) - - t.Run("rmdir non-empty fails", func(t *testing.T) { - err := fs.Mkdir(ctx, "/nonempty", 0o755) - if err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - err = fs.WriteFile(ctx, "/nonempty/file.txt", []byte("x"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - err = fs.Rmdir(ctx, "/nonempty") - var fsErr *FSError - if !errors.As(err, &fsErr) || fsErr.Code != ENOTEMPTY { - t.Errorf("Expected ENOTEMPTY, got %v", err) - } - }) - - t.Run("rename file", func(t *testing.T) { - err := fs.WriteFile(ctx, "/oldname.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - err = fs.Rename(ctx, "/oldname.txt", "/newname.txt") - if err != nil { - t.Fatalf("Rename failed: %v", err) - } - - _, err = fs.Stat(ctx, "/oldname.txt") - if !IsNotExist(err) { - t.Error("Old path still exists") - } - - data, err := fs.ReadFile(ctx, "/newname.txt") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - if string(data) != "content" { - t.Errorf("Content = %q, want %q", data, "content") - } - }) - - t.Run("link", func(t *testing.T) { - err := fs.WriteFile(ctx, "/original.txt", []byte("original"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - err = fs.Link(ctx, "/original.txt", "/linked.txt") - if err != nil { - t.Fatalf("Link failed: %v", err) - } - - origStats, _ := fs.Stat(ctx, "/original.txt") - linkStats, _ := fs.Stat(ctx, "/linked.txt") - - if origStats.Ino != linkStats.Ino { - t.Error("Hard link has different inode") - } - if origStats.Nlink != 2 { - t.Errorf("Nlink = %d, want 2", origStats.Nlink) - } - }) - - t.Run("symlink and readlink", func(t *testing.T) { - err := fs.Symlink(ctx, "/hello.txt", "/symlink.txt") - if err != nil { - t.Fatalf("Symlink failed: %v", err) - } - - // Stat follows the symlink to /hello.txt (a regular file) - stats, err := fs.Stat(ctx, "/symlink.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !stats.IsRegularFile() { - t.Error("Stat on symlink should follow to target (regular file)") - } - - // Lstat returns the symlink's own stats - lstats, err := fs.Lstat(ctx, "/symlink.txt") - if err != nil { - t.Fatalf("Lstat failed: %v", err) - } - if !lstats.IsSymlink() { - t.Error("Lstat should return symlink stats") - } - - target, err := fs.Readlink(ctx, "/symlink.txt") - if err != nil { - t.Fatalf("Readlink failed: %v", err) - } - if target != "/hello.txt" { - t.Errorf("Target = %q, want %q", target, "/hello.txt") - } - }) - - t.Run("chmod", func(t *testing.T) { - err := fs.WriteFile(ctx, "/chmod.txt", []byte("x"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - err = fs.Chmod(ctx, "/chmod.txt", 0o600) - if err != nil { - t.Fatalf("Chmod failed: %v", err) - } - - stats, _ := fs.Stat(ctx, "/chmod.txt") - if stats.Permissions() != 0o600 { - t.Errorf("Permissions = %o, want %o", stats.Permissions(), 0o600) - } - }) - - t.Run("utimes", func(t *testing.T) { - err := fs.WriteFile(ctx, "/utimes.txt", []byte("x"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - atime := int64(1000000) - mtime := int64(2000000) - err = fs.Utimes(ctx, "/utimes.txt", atime, mtime) - if err != nil { - t.Fatalf("Utimes failed: %v", err) - } - - stats, _ := fs.Stat(ctx, "/utimes.txt") - if stats.Atime != atime { - t.Errorf("Atime = %d, want %d", stats.Atime, atime) - } - if stats.Mtime != mtime { - t.Errorf("Mtime = %d, want %d", stats.Mtime, mtime) - } - }) -} - -func TestNanosecondTimestamps(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - t.Run("stat includes nanoseconds", func(t *testing.T) { - before := time.Now() - - err := fs.WriteFile(ctx, "/nsec_test.txt", []byte("test"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - after := time.Now() - - stats, err := fs.Stat(ctx, "/nsec_test.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - - // Verify seconds are within range - if stats.Mtime < before.Unix() || stats.Mtime > after.Unix() { - t.Errorf("Mtime %d not in range [%d, %d]", stats.Mtime, before.Unix(), after.Unix()) - } - - // Verify nanoseconds are valid (0-999999999) - if stats.MtimeNsec < 0 || stats.MtimeNsec > 999999999 { - t.Errorf("MtimeNsec %d out of valid range", stats.MtimeNsec) - } - if stats.AtimeNsec < 0 || stats.AtimeNsec > 999999999 { - t.Errorf("AtimeNsec %d out of valid range", stats.AtimeNsec) - } - if stats.CtimeNsec < 0 || stats.CtimeNsec > 999999999 { - t.Errorf("CtimeNsec %d out of valid range", stats.CtimeNsec) - } - - // Verify helper methods work - mtimeTime := stats.MtimeTime() - if mtimeTime.Before(before) || mtimeTime.After(after) { - t.Errorf("MtimeTime() %v not in range [%v, %v]", mtimeTime, before, after) - } - }) - - t.Run("UtimesNano sets nanoseconds", func(t *testing.T) { - err := fs.WriteFile(ctx, "/utimes_nsec.txt", []byte("test"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Set specific nanosecond values - atimeSec := int64(1000000000) - atimeNsec := int64(123456789) - mtimeSec := int64(1000000001) - mtimeNsec := int64(987654321) - - err = fs.UtimesNano(ctx, "/utimes_nsec.txt", atimeSec, atimeNsec, mtimeSec, mtimeNsec) - if err != nil { - t.Fatalf("UtimesNano failed: %v", err) - } - - stats, err := fs.Stat(ctx, "/utimes_nsec.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - - if stats.Atime != atimeSec { - t.Errorf("Atime = %d, want %d", stats.Atime, atimeSec) - } - if stats.AtimeNsec != atimeNsec { - t.Errorf("AtimeNsec = %d, want %d", stats.AtimeNsec, atimeNsec) - } - if stats.Mtime != mtimeSec { - t.Errorf("Mtime = %d, want %d", stats.Mtime, mtimeSec) - } - if stats.MtimeNsec != mtimeNsec { - t.Errorf("MtimeNsec = %d, want %d", stats.MtimeNsec, mtimeNsec) - } - }) - - t.Run("readdirPlus includes nanoseconds", func(t *testing.T) { - err := fs.Mkdir(ctx, "/nsec_dir", 0o755) - if err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - - err = fs.WriteFile(ctx, "/nsec_dir/file.txt", []byte("test"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - entries, err := fs.ReaddirPlus(ctx, "/nsec_dir") - if err != nil { - t.Fatalf("ReaddirPlus failed: %v", err) - } - - if len(entries) != 1 { - t.Fatalf("Expected 1 entry, got %d", len(entries)) - } - - stats := entries[0].Stats - // Verify nanoseconds are valid - if stats.MtimeNsec < 0 || stats.MtimeNsec > 999999999 { - t.Errorf("MtimeNsec %d out of valid range in ReaddirPlus", stats.MtimeNsec) - } - }) -} - -func TestFileHandle(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - t.Run("pread and pwrite", func(t *testing.T) { - _, f, err := fs.Create(ctx, "/filehandle.txt", 0o644) - if err != nil { - t.Fatalf("Create failed: %v", err) - } - defer f.Close() - - // Write some data - data := []byte("Hello, World!") - n, err := f.Pwrite(ctx, data, 0) - if err != nil { - t.Fatalf("Pwrite failed: %v", err) - } - if n != len(data) { - t.Errorf("Pwrite n = %d, want %d", n, len(data)) - } - - // Read it back - buf := make([]byte, len(data)) - n, err = f.Pread(ctx, buf, 0) - if err != nil { - t.Fatalf("Pread failed: %v", err) - } - if n != len(data) { - t.Errorf("Pread n = %d, want %d", n, len(data)) - } - if string(buf) != string(data) { - t.Errorf("Pread = %q, want %q", buf, data) - } - }) - - t.Run("pwrite at offset", func(t *testing.T) { - _, f, err := fs.Create(ctx, "/offset.txt", 0o644) - if err != nil { - t.Fatalf("Create failed: %v", err) - } - defer f.Close() - - // Write "AAAA" at offset 0 - f.Pwrite(ctx, []byte("AAAA"), 0) - - // Write "BB" at offset 10 - f.Pwrite(ctx, []byte("BB"), 10) - - stats, _ := f.Stat(ctx) - if stats.Size != 12 { - t.Errorf("Size = %d, want 12", stats.Size) - } - - // Read all data - buf := make([]byte, 12) - f.Pread(ctx, buf, 0) - - // Should be "AAAA" + 6 zeros + "BB" - expected := append([]byte("AAAA"), make([]byte, 6)...) - expected = append(expected, []byte("BB")...) - if string(buf) != string(expected) { - t.Errorf("Content mismatch: got %v, want %v", buf, expected) - } - }) - - t.Run("truncate", func(t *testing.T) { - _, f, err := fs.Create(ctx, "/truncate.txt", 0o644) - if err != nil { - t.Fatalf("Create failed: %v", err) - } - defer f.Close() - - f.Pwrite(ctx, []byte("Hello, World!"), 0) - - err = f.Truncate(ctx, 5) - if err != nil { - t.Fatalf("Truncate failed: %v", err) - } - - stats, _ := f.Stat(ctx) - if stats.Size != 5 { - t.Errorf("Size = %d, want 5", stats.Size) - } - - buf := make([]byte, 10) - n, _ := f.Pread(ctx, buf, 0) - if n != 5 { - t.Errorf("Read %d bytes, want 5", n) - } - if string(buf[:n]) != "Hello" { - t.Errorf("Content = %q, want %q", buf[:n], "Hello") - } - }) -} - -func TestLargeFile(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - // Create a file larger than one chunk (default 4096 bytes) - size := 10000 - data := make([]byte, size) - for i := range data { - data[i] = byte(i % 256) - } - - err := fs.WriteFile(ctx, "/large.bin", data, 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - readData, err := fs.ReadFile(ctx, "/large.bin") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - - if len(readData) != size { - t.Errorf("Size = %d, want %d", len(readData), size) - } - - for i := range data { - if readData[i] != data[i] { - t.Errorf("Byte %d: got %d, want %d", i, readData[i], data[i]) - break - } - } -} - -func TestKVStore(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - kv := afs.KV - - t.Run("set and get string", func(t *testing.T) { - err := kv.Set(ctx, "key1", "value1") - if err != nil { - t.Fatalf("Set failed: %v", err) - } - - var value string - err = kv.Get(ctx, "key1", &value) - if err != nil { - t.Fatalf("Get failed: %v", err) - } - if value != "value1" { - t.Errorf("Value = %q, want %q", value, "value1") - } - }) - - t.Run("set and get struct", func(t *testing.T) { - type Config struct { - Name string `json:"name"` - Version int `json:"version"` - } - - input := Config{Name: "test", Version: 42} - err := kv.Set(ctx, "config", input) - if err != nil { - t.Fatalf("Set failed: %v", err) - } - - var output Config - err = kv.Get(ctx, "config", &output) - if err != nil { - t.Fatalf("Get failed: %v", err) - } - if output != input { - t.Errorf("Value = %+v, want %+v", output, input) - } - }) - - t.Run("get non-existent key", func(t *testing.T) { - var value string - err := kv.Get(ctx, "nonexistent", &value) - if err == nil { - t.Error("Expected error for non-existent key") - } - }) - - t.Run("has", func(t *testing.T) { - kv.Set(ctx, "exists", "yes") - - has, err := kv.Has(ctx, "exists") - if err != nil { - t.Fatalf("Has failed: %v", err) - } - if !has { - t.Error("Has = false, want true") - } - - has, err = kv.Has(ctx, "notexists") - if err != nil { - t.Fatalf("Has failed: %v", err) - } - if has { - t.Error("Has = true, want false") - } - }) - - t.Run("delete", func(t *testing.T) { - kv.Set(ctx, "todelete", "value") - - err := kv.Delete(ctx, "todelete") - if err != nil { - t.Fatalf("Delete failed: %v", err) - } - - has, _ := kv.Has(ctx, "todelete") - if has { - t.Error("Key still exists after delete") - } - }) - - t.Run("keys with prefix", func(t *testing.T) { - kv.Set(ctx, "prefix:a", "1") - kv.Set(ctx, "prefix:b", "2") - kv.Set(ctx, "other:c", "3") - - keys, err := kv.Keys(ctx, "prefix:") - if err != nil { - t.Fatalf("Keys failed: %v", err) - } - - if len(keys) != 2 { - t.Errorf("Keys count = %d, want 2", len(keys)) - } - }) - - t.Run("list", func(t *testing.T) { - entries, err := kv.List(ctx, "") - if err != nil { - t.Fatalf("List failed: %v", err) - } - - if len(entries) == 0 { - t.Error("Expected some entries") - } - - for _, entry := range entries { - if entry.CreatedAt == 0 { - t.Errorf("Entry %q has zero CreatedAt", entry.Key) - } - } - }) - - t.Run("clear with prefix", func(t *testing.T) { - kv.Set(ctx, "clear:a", "1") - kv.Set(ctx, "clear:b", "2") - kv.Set(ctx, "keep:c", "3") - - err := kv.Clear(ctx, "clear:") - if err != nil { - t.Fatalf("Clear failed: %v", err) - } - - has, _ := kv.Has(ctx, "clear:a") - if has { - t.Error("clear:a still exists") - } - - has, _ = kv.Has(ctx, "keep:c") - if !has { - t.Error("keep:c was deleted") - } - }) -} - -func TestToolCalls(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - tools := afs.Tools - - t.Run("start and success", func(t *testing.T) { - pending, err := tools.Start(ctx, "test_tool", map[string]string{"arg": "value"}) - if err != nil { - t.Fatalf("Start failed: %v", err) - } - - time.Sleep(10 * time.Millisecond) // Small delay for duration - - call, err := pending.Success(ctx, map[string]int{"result": 42}) - if err != nil { - t.Fatalf("Success failed: %v", err) - } - - if call.ID == 0 { - t.Error("Call ID is 0") - } - if call.Name != "test_tool" { - t.Errorf("Name = %q, want %q", call.Name, "test_tool") - } - if call.Error != nil { - t.Error("Error should be nil for success") - } - }) - - t.Run("start and error", func(t *testing.T) { - pending, err := tools.Start(ctx, "failing_tool", nil) - if err != nil { - t.Fatalf("Start failed: %v", err) - } - - call, err := pending.Error(ctx, errors.New("something went wrong")) - if err != nil { - t.Fatalf("Error failed: %v", err) - } - - if call.Error == nil { - t.Error("Error should not be nil") - } - if *call.Error != "something went wrong" { - t.Errorf("Error = %q, want %q", *call.Error, "something went wrong") - } - }) - - t.Run("record", func(t *testing.T) { - now := time.Now().Unix() - call, err := tools.Record(ctx, "direct_tool", map[string]string{"a": "b"}, "result", nil, now-1, now) - if err != nil { - t.Fatalf("Record failed: %v", err) - } - - if call.DurationMs != 1000 { - t.Errorf("DurationMs = %d, want 1000", call.DurationMs) - } - }) - - t.Run("get by ID", func(t *testing.T) { - pending, _ := tools.Start(ctx, "get_test", nil) - original, _ := pending.Success(ctx, nil) - - retrieved, err := tools.Get(ctx, original.ID) - if err != nil { - t.Fatalf("Get failed: %v", err) - } - - if retrieved.Name != original.Name { - t.Errorf("Name = %q, want %q", retrieved.Name, original.Name) - } - }) - - t.Run("get by name", func(t *testing.T) { - calls, err := tools.GetByName(ctx, "test_tool", 10) - if err != nil { - t.Fatalf("GetByName failed: %v", err) - } - - if len(calls) == 0 { - t.Error("Expected at least one call") - } - - for _, call := range calls { - if call.Name != "test_tool" { - t.Errorf("Name = %q, want %q", call.Name, "test_tool") - } - } - }) - - t.Run("get recent", func(t *testing.T) { - since := time.Now().Unix() - 60 // Last minute - calls, err := tools.GetRecent(ctx, since, 100) - if err != nil { - t.Fatalf("GetRecent failed: %v", err) - } - - if len(calls) == 0 { - t.Error("Expected recent calls") - } - }) - - t.Run("get stats", func(t *testing.T) { - stats, err := tools.GetStats(ctx) - if err != nil { - t.Fatalf("GetStats failed: %v", err) - } - - if len(stats) == 0 { - t.Error("Expected stats") - } - - // Find test_tool stats - var found bool - for _, s := range stats { - if s.Name == "test_tool" { - found = true - if s.TotalCalls == 0 { - t.Error("TotalCalls is 0") - } - } - } - if !found { - t.Error("Stats for test_tool not found") - } - }) -} - -func TestErrors(t *testing.T) { - t.Run("FSError formatting", func(t *testing.T) { - err := ErrNoent("stat", "/missing") - if err.Error() != "stat /missing: no such file or directory" { - t.Errorf("Error = %q", err.Error()) - } - }) - - t.Run("IsNotExist", func(t *testing.T) { - err := ErrNoent("stat", "/missing") - if !IsNotExist(err) { - t.Error("IsNotExist should return true for ENOENT") - } - - err2 := ErrExist("mkdir", "/exists") - if IsNotExist(err2) { - t.Error("IsNotExist should return false for EEXIST") - } - }) -} - -func TestOpenWith(t *testing.T) { - ctx := context.Background() - - t.Run("basic usage", func(t *testing.T) { - db, err := sql.Open("sqlite", ":memory:") - if err != nil { - t.Fatalf("sql.Open failed: %v", err) - } - defer db.Close() - - afs, err := OpenWith(ctx, db) - if err != nil { - t.Fatalf("OpenWith failed: %v", err) - } - - // Subsystems should be initialized - if afs.FS == nil { - t.Fatal("FS is nil") - } - if afs.KV == nil { - t.Fatal("KV is nil") - } - if afs.Tools == nil { - t.Fatal("Tools is nil") - } - - // Path should be empty for OpenWith - if afs.Path() != "" { - t.Errorf("Path() = %q, want empty", afs.Path()) - } - - // DB should return the same connection - if afs.DB() != db { - t.Error("DB() did not return the provided connection") - } - }) - - t.Run("close does not close underlying db", func(t *testing.T) { - db, err := sql.Open("sqlite", ":memory:") - if err != nil { - t.Fatalf("sql.Open failed: %v", err) - } - defer db.Close() - - afs, err := OpenWith(ctx, db) - if err != nil { - t.Fatalf("OpenWith failed: %v", err) - } - - // Close the AgentFS - if err := afs.Close(); err != nil { - t.Fatalf("Close failed: %v", err) - } - - // The underlying DB should still be usable - var result int - if err := db.QueryRowContext(ctx, "SELECT 1").Scan(&result); err != nil { - t.Fatalf("DB should still work after AgentFS.Close: %v", err) - } - if result != 1 { - t.Errorf("got %d, want 1", result) - } - }) - - t.Run("filesystem operations work", func(t *testing.T) { - db, err := sql.Open("sqlite", ":memory:") - if err != nil { - t.Fatalf("sql.Open failed: %v", err) - } - defer db.Close() - - afs, err := OpenWith(ctx, db) - if err != nil { - t.Fatalf("OpenWith failed: %v", err) - } - - // Write and read a file - if err := afs.FS.WriteFile(ctx, "/hello.txt", []byte("hello"), 0o644); err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - data, err := afs.FS.ReadFile(ctx, "/hello.txt") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - if string(data) != "hello" { - t.Errorf("ReadFile = %q, want %q", data, "hello") - } - }) - - t.Run("kv operations work", func(t *testing.T) { - db, err := sql.Open("sqlite", ":memory:") - if err != nil { - t.Fatalf("sql.Open failed: %v", err) - } - defer db.Close() - - afs, err := OpenWith(ctx, db) - if err != nil { - t.Fatalf("OpenWith failed: %v", err) - } - - if err := afs.KV.Set(ctx, "key1", "value1"); err != nil { - t.Fatalf("KV.Set failed: %v", err) - } - - var val string - if err := afs.KV.Get(ctx, "key1", &val); err != nil { - t.Fatalf("KV.Get failed: %v", err) - } - if val != "value1" { - t.Errorf("KV.Get = %q, want %q", val, "value1") - } - }) - - t.Run("tool calls work", func(t *testing.T) { - db, err := sql.Open("sqlite", ":memory:") - if err != nil { - t.Fatalf("sql.Open failed: %v", err) - } - defer db.Close() - - afs, err := OpenWith(ctx, db) - if err != nil { - t.Fatalf("OpenWith failed: %v", err) - } - - pending, err := afs.Tools.Start(ctx, "test_tool", nil) - if err != nil { - t.Fatalf("Tools.Start failed: %v", err) - } - - call, err := pending.Success(ctx, "done") - if err != nil { - t.Fatalf("Success failed: %v", err) - } - if call.Name != "test_tool" { - t.Errorf("call.Name = %q, want %q", call.Name, "test_tool") - } - }) - - t.Run("with chunk size option", func(t *testing.T) { - db, err := sql.Open("sqlite", ":memory:") - if err != nil { - t.Fatalf("sql.Open failed: %v", err) - } - defer db.Close() - - afs, err := OpenWith(ctx, db, WithChunkSize(8192)) - if err != nil { - t.Fatalf("OpenWith failed: %v", err) - } - - if afs.FS.ChunkSize() != 8192 { - t.Errorf("ChunkSize() = %d, want 8192", afs.FS.ChunkSize()) - } - }) - - t.Run("shared database between two AgentFS instances", func(t *testing.T) { - db, err := sql.Open("sqlite", ":memory:") - if err != nil { - t.Fatalf("sql.Open failed: %v", err) - } - defer db.Close() - - afs1, err := OpenWith(ctx, db) - if err != nil { - t.Fatalf("OpenWith(1) failed: %v", err) - } - - afs2, err := OpenWith(ctx, db) - if err != nil { - t.Fatalf("OpenWith(2) failed: %v", err) - } - - // Write from instance 1, read from instance 2 - if err := afs1.FS.WriteFile(ctx, "/shared.txt", []byte("shared"), 0o644); err != nil { - t.Fatalf("WriteFile via afs1 failed: %v", err) - } - - data, err := afs2.FS.ReadFile(ctx, "/shared.txt") - if err != nil { - t.Fatalf("ReadFile via afs2 failed: %v", err) - } - if string(data) != "shared" { - t.Errorf("ReadFile = %q, want %q", data, "shared") - } - }) -} - -func TestOpenClosesDBOnError(t *testing.T) { - ctx := context.Background() - - // Open normally first to create a valid database, then tamper with schema version - tmpDir := t.TempDir() - dbPath := filepath.Join(tmpDir, "test.db") - - afs, err := Open(ctx, AgentFSOptions{Path: dbPath}) - if err != nil { - t.Fatalf("Open failed: %v", err) - } - afs.Close() - - // Tamper with the schema version - db, err := sql.Open("sqlite", dbPath) - if err != nil { - t.Fatalf("sql.Open failed: %v", err) - } - _, err = db.ExecContext(ctx, "UPDATE fs_config SET value = '9.9' WHERE key = 'schema_version'") - if err != nil { - t.Fatalf("UPDATE failed: %v", err) - } - db.Close() - - // Re-opening should fail with schema version mismatch - _, err = Open(ctx, AgentFSOptions{Path: dbPath}) - if err == nil { - t.Fatal("Expected error for schema version mismatch") - } - - var mismatch *ErrSchemaVersionMismatch - if !errors.As(err, &mismatch) { - t.Fatalf("Expected ErrSchemaVersionMismatch, got: %v", err) - } -} - -func TestSchemaVersionValidation(t *testing.T) { - ctx := context.Background() - - t.Run("fresh database sets schema version", func(t *testing.T) { - db, err := sql.Open("sqlite", ":memory:") - if err != nil { - t.Fatalf("sql.Open failed: %v", err) - } - defer db.Close() - - _, err = OpenWith(ctx, db) - if err != nil { - t.Fatalf("OpenWith failed: %v", err) - } - - // Verify schema version was set - var version string - if err := db.QueryRowContext(ctx, "SELECT value FROM fs_config WHERE key = 'schema_version'").Scan(&version); err != nil { - t.Fatalf("Failed to query schema_version: %v", err) - } - if version != schemaVersion { - t.Errorf("schema_version = %q, want %q", version, schemaVersion) - } - }) - - t.Run("compatible version succeeds", func(t *testing.T) { - db, err := sql.Open("sqlite", ":memory:") - if err != nil { - t.Fatalf("sql.Open failed: %v", err) - } - defer db.Close() - - // First open initializes - afs1, err := OpenWith(ctx, db) - if err != nil { - t.Fatalf("OpenWith(1) failed: %v", err) - } - _ = afs1 - - // Second open should succeed (same version) - _, err = OpenWith(ctx, db) - if err != nil { - t.Fatalf("OpenWith(2) failed: %v", err) - } - }) - - t.Run("incompatible version fails", func(t *testing.T) { - db, err := sql.Open("sqlite", ":memory:") - if err != nil { - t.Fatalf("sql.Open failed: %v", err) - } - defer db.Close() - - // Initialize schema manually with a different version - if err := initSchema(ctx, db); err != nil { - t.Fatalf("initSchema failed: %v", err) - } - if _, err := db.ExecContext(ctx, "INSERT INTO fs_config (key, value) VALUES ('schema_version', '99.0')"); err != nil { - t.Fatalf("INSERT failed: %v", err) - } - - // OpenWith should fail - _, err = OpenWith(ctx, db) - if err == nil { - t.Fatal("Expected error for incompatible schema version") - } - - var mismatch *ErrSchemaVersionMismatch - if !errors.As(err, &mismatch) { - t.Fatalf("Expected ErrSchemaVersionMismatch, got: %T: %v", err, err) - } - if mismatch.Found != "99.0" { - t.Errorf("Found = %q, want %q", mismatch.Found, "99.0") - } - if mismatch.Expected != schemaVersion { - t.Errorf("Expected = %q, want %q", mismatch.Expected, schemaVersion) - } - }) - - t.Run("error message format", func(t *testing.T) { - err := &ErrSchemaVersionMismatch{Found: "0.1", Expected: "0.4"} - msg := err.Error() - if !strings.Contains(msg, "0.1") || !strings.Contains(msg, "0.4") { - t.Errorf("Error message missing versions: %q", msg) - } - }) -} - -func TestErrorVariants(t *testing.T) { - t.Run("ErrNameTooLong", func(t *testing.T) { - err := ErrNameTooLong("mkdir", "/foo/longname") - if err.Code != ENAMETOOLONG { - t.Errorf("Code = %d, want %d (ENAMETOOLONG)", err.Code, ENAMETOOLONG) - } - if err.Syscall != "mkdir" { - t.Errorf("Syscall = %q, want %q", err.Syscall, "mkdir") - } - msg := err.Error() - if !strings.Contains(msg, "file name too long") { - t.Errorf("Error message should contain 'file name too long': %q", msg) - } - }) - - t.Run("ErrRootOperation", func(t *testing.T) { - err := ErrRootOperation("rmdir", "/") - if err.Code != EPERM { - t.Errorf("Code = %d, want %d (EPERM)", err.Code, EPERM) - } - msg := err.Error() - if !strings.Contains(msg, "cannot modify root") { - t.Errorf("Error message should contain 'cannot modify root': %q", msg) - } - }) - - t.Run("ErrInvalidRename", func(t *testing.T) { - err := ErrInvalidRename("rename", "/foo") - if err.Code != EINVAL { - t.Errorf("Code = %d, want %d (EINVAL)", err.Code, EINVAL) - } - msg := err.Error() - if !strings.Contains(msg, "subtree") { - t.Errorf("Error message should mention subtree: %q", msg) - } - }) - - t.Run("ErrNotSymlink", func(t *testing.T) { - err := ErrNotSymlink("readlink", "/foo") - if err.Code != EINVAL { - t.Errorf("Code = %d, want %d (EINVAL)", err.Code, EINVAL) - } - msg := err.Error() - if !strings.Contains(msg, "not a symbolic link") { - t.Errorf("Error message should mention 'not a symbolic link': %q", msg) - } - }) - - t.Run("IsNameTooLong", func(t *testing.T) { - err := ErrNameTooLong("mkdir", "/toolong") - if !IsNameTooLong(err) { - t.Error("IsNameTooLong should return true for ENAMETOOLONG errors") - } - if IsNameTooLong(ErrNoent("stat", "/foo")) { - t.Error("IsNameTooLong should return false for ENOENT errors") - } - }) - - t.Run("ENAMETOOLONG constant", func(t *testing.T) { - if ENAMETOOLONG != 36 { - t.Errorf("ENAMETOOLONG = %d, want 36", ENAMETOOLONG) - } - }) - - t.Run("MaxNameLen constant", func(t *testing.T) { - if MaxNameLen != 255 { - t.Errorf("MaxNameLen = %d, want 255", MaxNameLen) - } - }) - - t.Run("ENAMETOOLONG in codeMessage", func(t *testing.T) { - err := &FSError{Code: ENAMETOOLONG, Syscall: "test", Path: "/x"} - msg := err.Error() - if !strings.Contains(msg, "file name too long") { - t.Errorf("codeMessage for ENAMETOOLONG should produce 'file name too long': %q", msg) - } - }) -} - -func TestNameLengthValidation(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - longName := strings.Repeat("a", MaxNameLen+1) // 256 bytes - - t.Run("Mkdir rejects long name", func(t *testing.T) { - err := fs.Mkdir(ctx, "/"+longName, 0o755) - if err == nil { - t.Fatal("Expected error for long directory name") - } - if !IsNameTooLong(err) { - t.Errorf("Expected ENAMETOOLONG, got: %v", err) - } - }) - - t.Run("Mkdir accepts max length name", func(t *testing.T) { - okName := strings.Repeat("b", MaxNameLen) - err := fs.Mkdir(ctx, "/"+okName, 0o755) - if err != nil { - t.Fatalf("Mkdir with 255-byte name should succeed: %v", err) - } - }) - - t.Run("WriteFile rejects long name", func(t *testing.T) { - err := fs.WriteFile(ctx, "/"+longName, []byte("data"), 0o644) - if err == nil { - t.Fatal("Expected error for long file name") - } - if !IsNameTooLong(err) { - t.Errorf("Expected ENAMETOOLONG, got: %v", err) - } - }) - - t.Run("Create rejects long name", func(t *testing.T) { - _, _, err := fs.Create(ctx, "/"+longName, 0o644) - if err == nil { - t.Fatal("Expected error for long file name") - } - if !IsNameTooLong(err) { - t.Errorf("Expected ENAMETOOLONG, got: %v", err) - } - }) - - t.Run("Link rejects long new name", func(t *testing.T) { - if err := fs.WriteFile(ctx, "/link_src.txt", []byte("data"), 0o644); err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - err := fs.Link(ctx, "/link_src.txt", "/"+longName) - if err == nil { - t.Fatal("Expected error for long link name") - } - if !IsNameTooLong(err) { - t.Errorf("Expected ENAMETOOLONG, got: %v", err) - } - }) - - t.Run("Symlink rejects long link name", func(t *testing.T) { - err := fs.Symlink(ctx, "/target", "/"+longName) - if err == nil { - t.Fatal("Expected error for long symlink name") - } - if !IsNameTooLong(err) { - t.Errorf("Expected ENAMETOOLONG, got: %v", err) - } - }) - - t.Run("Rename rejects long new name", func(t *testing.T) { - if err := fs.WriteFile(ctx, "/rename_src.txt", []byte("data"), 0o644); err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - err := fs.Rename(ctx, "/rename_src.txt", "/"+longName) - if err == nil { - t.Fatal("Expected error for long new name") - } - if !IsNameTooLong(err) { - t.Errorf("Expected ENAMETOOLONG, got: %v", err) - } - }) -} - -func TestRootOperationGuards(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - t.Run("Unlink root", func(t *testing.T) { - err := fs.Unlink(ctx, "/") - if err == nil { - t.Fatal("Expected error when unlinking root") - } - var fsErr *FSError - if !errors.As(err, &fsErr) { - t.Fatalf("Expected FSError, got: %T", err) - } - if fsErr.Code != EPERM { - t.Errorf("Code = %d, want %d (EPERM)", fsErr.Code, EPERM) - } - if !strings.Contains(fsErr.Message, "root") { - t.Errorf("Message should mention root: %q", fsErr.Message) - } - }) - - t.Run("Rmdir root", func(t *testing.T) { - err := fs.Rmdir(ctx, "/") - if err == nil { - t.Fatal("Expected error when removing root directory") - } - var fsErr *FSError - if !errors.As(err, &fsErr) { - t.Fatalf("Expected FSError, got: %T", err) - } - if fsErr.Code != EPERM { - t.Errorf("Code = %d, want %d (EPERM)", fsErr.Code, EPERM) - } - }) - - t.Run("Rename from root", func(t *testing.T) { - err := fs.Rename(ctx, "/", "/newroot") - if err == nil { - t.Fatal("Expected error when renaming root") - } - var fsErr *FSError - if !errors.As(err, &fsErr) { - t.Fatalf("Expected FSError, got: %T", err) - } - if fsErr.Code != EPERM { - t.Errorf("Code = %d, want %d (EPERM)", fsErr.Code, EPERM) - } - }) - - t.Run("Rename to root", func(t *testing.T) { - if err := fs.WriteFile(ctx, "/file.txt", []byte("data"), 0o644); err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - err := fs.Rename(ctx, "/file.txt", "/") - if err == nil { - t.Fatal("Expected error when renaming to root") - } - }) -} - -func TestSubtreeRenamePrevention(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - // Create directory structure: /a/b/c - if err := fs.MkdirAll(ctx, "/a/b/c", 0o755); err != nil { - t.Fatalf("MkdirAll failed: %v", err) - } - - t.Run("rename dir into own subtree", func(t *testing.T) { - err := fs.Rename(ctx, "/a", "/a/b/new_a") - if err == nil { - t.Fatal("Expected error when renaming directory into its own subtree") - } - var fsErr *FSError - if !errors.As(err, &fsErr) { - t.Fatalf("Expected FSError, got: %T: %v", err, err) - } - if fsErr.Code != EINVAL { - t.Errorf("Code = %d, want %d (EINVAL)", fsErr.Code, EINVAL) - } - if !strings.Contains(fsErr.Message, "subtree") { - t.Errorf("Message should mention subtree: %q", fsErr.Message) - } - }) - - t.Run("rename dir into deeply nested subtree", func(t *testing.T) { - err := fs.Rename(ctx, "/a", "/a/b/c/moved") - if err == nil { - t.Fatal("Expected error when renaming into deeply nested subtree") - } - var fsErr *FSError - if !errors.As(err, &fsErr) { - t.Fatalf("Expected FSError, got: %v", err) - } - }) - - t.Run("rename dir to sibling is allowed", func(t *testing.T) { - // Create /x/child - if err := fs.MkdirAll(ctx, "/x/child", 0o755); err != nil { - t.Fatalf("MkdirAll failed: %v", err) - } - - // Rename /x to /y should work (sibling, not subtree) - err := fs.Rename(ctx, "/x", "/y") - if err != nil { - t.Fatalf("Rename to sibling should succeed: %v", err) - } - - // Verify /y/child exists - stats, err := fs.Stat(ctx, "/y/child") - if err != nil { - t.Fatalf("Stat /y/child failed: %v", err) - } - if !stats.IsDir() { - t.Error("/y/child should be a directory") - } - }) - - t.Run("rename file into dir with same prefix is allowed", func(t *testing.T) { - // /ab should not be considered a subtree of /a - if err := fs.Mkdir(ctx, "/ab", 0o755); err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - if err := fs.WriteFile(ctx, "/a/test.txt", []byte("test"), 0o644); err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - err := fs.Rename(ctx, "/a/test.txt", "/ab/test.txt") - if err != nil { - t.Fatalf("Rename to /ab/test.txt should succeed (not a subtree of /a): %v", err) - } - }) -} - -func TestReadlinkOnNonSymlink(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - t.Run("readlink on regular file", func(t *testing.T) { - if err := fs.WriteFile(ctx, "/regular.txt", []byte("data"), 0o644); err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - _, err := fs.Readlink(ctx, "/regular.txt") - if err == nil { - t.Fatal("Expected error when readlink on regular file") - } - - var fsErr *FSError - if !errors.As(err, &fsErr) { - t.Fatalf("Expected FSError, got: %T", err) - } - if fsErr.Code != EINVAL { - t.Errorf("Code = %d, want %d (EINVAL)", fsErr.Code, EINVAL) - } - if !strings.Contains(fsErr.Message, "not a symbolic link") { - t.Errorf("Message should mention 'not a symbolic link': %q", fsErr.Message) - } - }) - - t.Run("readlink on directory", func(t *testing.T) { - if err := fs.Mkdir(ctx, "/mydir", 0o755); err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - - _, err := fs.Readlink(ctx, "/mydir") - if err == nil { - t.Fatal("Expected error when readlink on directory") - } - }) - - t.Run("readlink on actual symlink succeeds", func(t *testing.T) { - if err := fs.Symlink(ctx, "/target", "/mylink"); err != nil { - t.Fatalf("Symlink failed: %v", err) - } - - target, err := fs.Readlink(ctx, "/mylink") - if err != nil { - t.Fatalf("Readlink failed: %v", err) - } - if target != "/target" { - t.Errorf("Readlink = %q, want %q", target, "/target") - } - }) -} - -func TestLstat(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - t.Run("regular file", func(t *testing.T) { - if err := fs.WriteFile(ctx, "/lstat_file.txt", []byte("hello"), 0o644); err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - stats, err := fs.Lstat(ctx, "/lstat_file.txt") - if err != nil { - t.Fatalf("Lstat failed: %v", err) - } - - if !stats.IsRegularFile() { - t.Errorf("expected regular file, got mode %o", stats.Mode) - } - if stats.Size != 5 { - t.Errorf("Size = %d, want 5", stats.Size) - } - }) - - t.Run("directory", func(t *testing.T) { - if err := fs.Mkdir(ctx, "/lstat_dir", 0o755); err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - - stats, err := fs.Lstat(ctx, "/lstat_dir") - if err != nil { - t.Fatalf("Lstat failed: %v", err) - } - - if !stats.IsDir() { - t.Errorf("expected directory, got mode %o", stats.Mode) - } - }) - - t.Run("symlink returns symlink stats", func(t *testing.T) { - if err := fs.Symlink(ctx, "/some_target", "/lstat_link"); err != nil { - t.Fatalf("Symlink failed: %v", err) - } - - stats, err := fs.Lstat(ctx, "/lstat_link") - if err != nil { - t.Fatalf("Lstat failed: %v", err) - } - - if !stats.IsSymlink() { - t.Errorf("expected symlink, got mode %o", stats.Mode) - } - }) - - t.Run("nonexistent path", func(t *testing.T) { - _, err := fs.Lstat(ctx, "/no_such_file") - if err == nil { - t.Fatal("Expected error for nonexistent path") - } - if !IsNotExist(err) { - t.Errorf("Expected ENOENT, got: %v", err) - } - }) - - t.Run("matches Stat for regular files", func(t *testing.T) { - statResult, err := fs.Stat(ctx, "/lstat_file.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - - lstatResult, err := fs.Lstat(ctx, "/lstat_file.txt") - if err != nil { - t.Fatalf("Lstat failed: %v", err) - } - - if statResult.Ino != lstatResult.Ino { - t.Errorf("Ino mismatch: Stat=%d, Lstat=%d", statResult.Ino, lstatResult.Ino) - } - if statResult.Mode != lstatResult.Mode { - t.Errorf("Mode mismatch: Stat=%o, Lstat=%o", statResult.Mode, lstatResult.Mode) - } - }) -} - -func TestStatfs(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - t.Run("empty filesystem has root inode", func(t *testing.T) { - stats, err := fs.Statfs(ctx) - if err != nil { - t.Fatalf("Statfs failed: %v", err) - } - - // At minimum, root inode exists - if stats.Inodes < 1 { - t.Errorf("Inodes = %d, want >= 1", stats.Inodes) - } - - if stats.BytesUsed != 0 { - t.Errorf("BytesUsed = %d, want 0 for empty filesystem", stats.BytesUsed) - } - }) - - t.Run("counts inodes correctly", func(t *testing.T) { - initialStats, err := fs.Statfs(ctx) - if err != nil { - t.Fatalf("Statfs failed: %v", err) - } - - // Create a directory and a file - if err := fs.Mkdir(ctx, "/statfs_dir", 0o755); err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - if err := fs.WriteFile(ctx, "/statfs_file.txt", []byte("content"), 0o644); err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - afterStats, err := fs.Statfs(ctx) - if err != nil { - t.Fatalf("Statfs failed: %v", err) - } - - expectedInodes := initialStats.Inodes + 2 - if afterStats.Inodes != expectedInodes { - t.Errorf("Inodes = %d, want %d", afterStats.Inodes, expectedInodes) - } - }) - - t.Run("tracks bytes used", func(t *testing.T) { - if err := fs.WriteFile(ctx, "/statfs_data.bin", []byte("1234567890"), 0o644); err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - stats, err := fs.Statfs(ctx) - if err != nil { - t.Fatalf("Statfs failed: %v", err) - } - - // BytesUsed should include the 10 bytes we just wrote plus the 7 from "content" - if stats.BytesUsed < 17 { - t.Errorf("BytesUsed = %d, want >= 17", stats.BytesUsed) - } - }) - - t.Run("bytes decrease after unlink", func(t *testing.T) { - beforeStats, err := fs.Statfs(ctx) - if err != nil { - t.Fatalf("Statfs failed: %v", err) - } - - if err := fs.Unlink(ctx, "/statfs_data.bin"); err != nil { - t.Fatalf("Unlink failed: %v", err) - } - - afterStats, err := fs.Statfs(ctx) - if err != nil { - t.Fatalf("Statfs failed: %v", err) - } - - if afterStats.Inodes >= beforeStats.Inodes { - t.Errorf("Inodes should decrease after unlink: before=%d, after=%d", beforeStats.Inodes, afterStats.Inodes) - } - if afterStats.BytesUsed >= beforeStats.BytesUsed { - t.Errorf("BytesUsed should decrease after unlink: before=%d, after=%d", beforeStats.BytesUsed, afterStats.BytesUsed) - } - }) - - t.Run("symlinks count as inodes", func(t *testing.T) { - beforeStats, err := fs.Statfs(ctx) - if err != nil { - t.Fatalf("Statfs failed: %v", err) - } - - if err := fs.Symlink(ctx, "/target", "/statfs_symlink"); err != nil { - t.Fatalf("Symlink failed: %v", err) - } - - afterStats, err := fs.Statfs(ctx) - if err != nil { - t.Fatalf("Statfs failed: %v", err) - } - - if afterStats.Inodes != beforeStats.Inodes+1 { - t.Errorf("Inodes = %d, want %d", afterStats.Inodes, beforeStats.Inodes+1) - } - }) -} - -func TestChown(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - t.Run("change uid and gid", func(t *testing.T) { - if err := fs.WriteFile(ctx, "/chown_file.txt", []byte("data"), 0o644); err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - if err := fs.Chown(ctx, "/chown_file.txt", 1000, 2000); err != nil { - t.Fatalf("Chown failed: %v", err) - } - - stats, err := fs.Stat(ctx, "/chown_file.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if stats.UID != 1000 { - t.Errorf("UID = %d, want 1000", stats.UID) - } - if stats.GID != 2000 { - t.Errorf("GID = %d, want 2000", stats.GID) - } - }) - - t.Run("change uid only", func(t *testing.T) { - if err := fs.Chown(ctx, "/chown_file.txt", 500, -1); err != nil { - t.Fatalf("Chown failed: %v", err) - } - - stats, err := fs.Stat(ctx, "/chown_file.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if stats.UID != 500 { - t.Errorf("UID = %d, want 500", stats.UID) - } - if stats.GID != 2000 { - t.Errorf("GID = %d, want 2000 (unchanged)", stats.GID) - } - }) - - t.Run("change gid only", func(t *testing.T) { - if err := fs.Chown(ctx, "/chown_file.txt", -1, 999); err != nil { - t.Fatalf("Chown failed: %v", err) - } - - stats, err := fs.Stat(ctx, "/chown_file.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if stats.UID != 500 { - t.Errorf("UID = %d, want 500 (unchanged)", stats.UID) - } - if stats.GID != 999 { - t.Errorf("GID = %d, want 999", stats.GID) - } - }) - - t.Run("both -1 is a no-op", func(t *testing.T) { - beforeStats, err := fs.Stat(ctx, "/chown_file.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - - if err := fs.Chown(ctx, "/chown_file.txt", -1, -1); err != nil { - t.Fatalf("Chown(-1, -1) failed: %v", err) - } - - afterStats, err := fs.Stat(ctx, "/chown_file.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - - if afterStats.UID != beforeStats.UID { - t.Errorf("UID changed: %d -> %d", beforeStats.UID, afterStats.UID) - } - if afterStats.GID != beforeStats.GID { - t.Errorf("GID changed: %d -> %d", beforeStats.GID, afterStats.GID) - } - }) - - t.Run("updates ctime", func(t *testing.T) { - beforeStats, err := fs.Stat(ctx, "/chown_file.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - - time.Sleep(10 * time.Millisecond) - - if err := fs.Chown(ctx, "/chown_file.txt", 42, 42); err != nil { - t.Fatalf("Chown failed: %v", err) - } - - afterStats, err := fs.Stat(ctx, "/chown_file.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - - if afterStats.Ctime < beforeStats.Ctime { - t.Errorf("Ctime should not decrease: before=%d, after=%d", beforeStats.Ctime, afterStats.Ctime) - } - if afterStats.Ctime == beforeStats.Ctime && afterStats.CtimeNsec <= beforeStats.CtimeNsec { - t.Error("Ctime should have been updated") - } - }) - - t.Run("chown on directory", func(t *testing.T) { - if err := fs.Mkdir(ctx, "/chown_dir", 0o755); err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - - if err := fs.Chown(ctx, "/chown_dir", 100, 200); err != nil { - t.Fatalf("Chown on directory failed: %v", err) - } - - stats, err := fs.Stat(ctx, "/chown_dir") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if stats.UID != 100 || stats.GID != 200 { - t.Errorf("UID=%d GID=%d, want UID=100 GID=200", stats.UID, stats.GID) - } - }) - - t.Run("chown on nonexistent file", func(t *testing.T) { - err := fs.Chown(ctx, "/no_such_chown_file", 1, 1) - if err == nil { - t.Fatal("Expected error for nonexistent file") - } - if !IsNotExist(err) { - t.Errorf("Expected ENOENT, got: %v", err) - } - }) -} - -func TestMknod(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - t.Run("create FIFO", func(t *testing.T) { - err := fs.Mknod(ctx, "/my_fifo", S_IFIFO|0o644, 0) - if err != nil { - t.Fatalf("Mknod FIFO failed: %v", err) - } - - stats, err := fs.Stat(ctx, "/my_fifo") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !stats.IsFIFO() { - t.Errorf("expected FIFO, got mode %o", stats.Mode) - } - if stats.Permissions() != 0o644 { - t.Errorf("Permissions = %o, want 644", stats.Permissions()) - } - if stats.Nlink != 1 { - t.Errorf("Nlink = %d, want 1", stats.Nlink) - } - }) - - t.Run("create character device", func(t *testing.T) { - err := fs.Mknod(ctx, "/my_chardev", S_IFCHR|0o660, 0x0501) // major=5, minor=1 - if err != nil { - t.Fatalf("Mknod char device failed: %v", err) - } - - stats, err := fs.Stat(ctx, "/my_chardev") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !stats.IsCharDevice() { - t.Errorf("expected char device, got mode %o", stats.Mode) - } - if stats.Rdev != 0x0501 { - t.Errorf("Rdev = %d, want %d", stats.Rdev, 0x0501) - } - }) - - t.Run("create block device", func(t *testing.T) { - err := fs.Mknod(ctx, "/my_blkdev", S_IFBLK|0o660, 0x0800) // major=8, minor=0 - if err != nil { - t.Fatalf("Mknod block device failed: %v", err) - } - - stats, err := fs.Stat(ctx, "/my_blkdev") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !stats.IsBlockDevice() { - t.Errorf("expected block device, got mode %o", stats.Mode) - } - if stats.Rdev != 0x0800 { - t.Errorf("Rdev = %d, want %d", stats.Rdev, 0x0800) - } - }) - - t.Run("create socket", func(t *testing.T) { - err := fs.Mknod(ctx, "/my_sock", S_IFSOCK|0o755, 0) - if err != nil { - t.Fatalf("Mknod socket failed: %v", err) - } - - stats, err := fs.Stat(ctx, "/my_sock") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !stats.IsSocket() { - t.Errorf("expected socket, got mode %o", stats.Mode) - } - }) - - t.Run("rejects invalid mode without special file type", func(t *testing.T) { - // S_IFREG is not valid for mknod - err := fs.Mknod(ctx, "/bad_mode", S_IFREG|0o644, 0) - if err == nil { - t.Fatal("Expected error for invalid mode") - } - var fsErr *FSError - if !errors.As(err, &fsErr) { - t.Fatalf("Expected FSError, got: %T", err) - } - if fsErr.Code != EINVAL { - t.Errorf("Code = %d, want %d (EINVAL)", fsErr.Code, EINVAL) - } - }) - - t.Run("rejects directory mode", func(t *testing.T) { - err := fs.Mknod(ctx, "/bad_dir", S_IFDIR|0o755, 0) - if err == nil { - t.Fatal("Expected error for directory mode") - } - }) - - t.Run("rejects symlink mode", func(t *testing.T) { - err := fs.Mknod(ctx, "/bad_symlink", S_IFLNK|0o777, 0) - if err == nil { - t.Fatal("Expected error for symlink mode") - } - }) - - t.Run("rejects plain permission bits (no file type)", func(t *testing.T) { - err := fs.Mknod(ctx, "/no_type", 0o644, 0) - if err == nil { - t.Fatal("Expected error when no file type in mode") - } - }) - - t.Run("already exists", func(t *testing.T) { - err := fs.Mknod(ctx, "/my_fifo", S_IFIFO|0o644, 0) - if err == nil { - t.Fatal("Expected EEXIST error") - } - if !IsExist(err) { - t.Errorf("Expected EEXIST, got: %v", err) - } - }) - - t.Run("parent not found", func(t *testing.T) { - err := fs.Mknod(ctx, "/no_parent/fifo", S_IFIFO|0o644, 0) - if err == nil { - t.Fatal("Expected error for nonexistent parent") - } - if !IsNotExist(err) { - t.Errorf("Expected ENOENT, got: %v", err) - } - }) - - t.Run("parent not a directory", func(t *testing.T) { - if err := fs.WriteFile(ctx, "/mknod_file", []byte("data"), 0o644); err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - err := fs.Mknod(ctx, "/mknod_file/fifo", S_IFIFO|0o644, 0) - if err == nil { - t.Fatal("Expected error when parent is not a directory") - } - }) - - t.Run("root path rejected", func(t *testing.T) { - err := fs.Mknod(ctx, "/", S_IFIFO|0o644, 0) - if err == nil { - t.Fatal("Expected error for root path") - } - var fsErr *FSError - if !errors.As(err, &fsErr) { - t.Fatalf("Expected FSError, got: %T", err) - } - if fsErr.Code != EPERM { - t.Errorf("Code = %d, want %d (EPERM)", fsErr.Code, EPERM) - } - }) - - t.Run("name too long", func(t *testing.T) { - longName := strings.Repeat("x", MaxNameLen+1) - err := fs.Mknod(ctx, "/"+longName, S_IFIFO|0o644, 0) - if err == nil { - t.Fatal("Expected ENAMETOOLONG") - } - if !IsNameTooLong(err) { - t.Errorf("Expected ENAMETOOLONG, got: %v", err) - } - }) - - t.Run("create in subdirectory", func(t *testing.T) { - if err := fs.Mkdir(ctx, "/mknod_subdir", 0o755); err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - - err := fs.Mknod(ctx, "/mknod_subdir/pipe", S_IFIFO|0o644, 0) - if err != nil { - t.Fatalf("Mknod in subdir failed: %v", err) - } - - stats, err := fs.Stat(ctx, "/mknod_subdir/pipe") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !stats.IsFIFO() { - t.Errorf("expected FIFO, got mode %o", stats.Mode) - } - }) - - t.Run("visible in readdir", func(t *testing.T) { - entries, err := fs.Readdir(ctx, "/mknod_subdir") - if err != nil { - t.Fatalf("Readdir failed: %v", err) - } - - found := false - for _, name := range entries { - if name == "pipe" { - found = true - break - } - } - if !found { - t.Errorf("mknod entry 'pipe' not found in readdir results: %v", entries) - } - }) - - t.Run("statfs counts mknod inodes", func(t *testing.T) { - beforeStats, err := fs.Statfs(ctx) - if err != nil { - t.Fatalf("Statfs failed: %v", err) - } - - if err := fs.Mknod(ctx, "/counted_fifo", S_IFIFO|0o644, 0); err != nil { - t.Fatalf("Mknod failed: %v", err) - } - - afterStats, err := fs.Statfs(ctx) - if err != nil { - t.Fatalf("Statfs failed: %v", err) - } - - if afterStats.Inodes != beforeStats.Inodes+1 { - t.Errorf("Inodes = %d, want %d", afterStats.Inodes, beforeStats.Inodes+1) - } - }) -} - -// setupTestDB creates a temporary database for testing -func TestUtimens(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - t.Run("TimeSet sets both timestamps", func(t *testing.T) { - err := fs.WriteFile(ctx, "/utimens_set.txt", []byte("x"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - err = fs.Utimens(ctx, "/utimens_set.txt", - TimeSet(1000, 123456789), - TimeSet(2000, 987654321), - ) - if err != nil { - t.Fatalf("Utimens failed: %v", err) - } - - stats, _ := fs.Stat(ctx, "/utimens_set.txt") - if stats.Atime != 1000 { - t.Errorf("Atime = %d, want 1000", stats.Atime) - } - if stats.AtimeNsec != 123456789 { - t.Errorf("AtimeNsec = %d, want 123456789", stats.AtimeNsec) - } - if stats.Mtime != 2000 { - t.Errorf("Mtime = %d, want 2000", stats.Mtime) - } - if stats.MtimeNsec != 987654321 { - t.Errorf("MtimeNsec = %d, want 987654321", stats.MtimeNsec) - } - }) - - t.Run("TimeOmit leaves timestamp unchanged", func(t *testing.T) { - err := fs.WriteFile(ctx, "/utimens_omit.txt", []byte("x"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Set known values first - err = fs.Utimens(ctx, "/utimens_omit.txt", - TimeSet(1000, 111), - TimeSet(2000, 222), - ) - if err != nil { - t.Fatalf("Utimens failed: %v", err) - } - - // Now omit atime, change mtime - err = fs.Utimens(ctx, "/utimens_omit.txt", - TimeOmit(), - TimeSet(3000, 333), - ) - if err != nil { - t.Fatalf("Utimens failed: %v", err) - } - - stats, _ := fs.Stat(ctx, "/utimens_omit.txt") - if stats.Atime != 1000 || stats.AtimeNsec != 111 { - t.Errorf("Atime changed: %d.%d, want 1000.111", stats.Atime, stats.AtimeNsec) - } - if stats.Mtime != 3000 || stats.MtimeNsec != 333 { - t.Errorf("Mtime = %d.%d, want 3000.333", stats.Mtime, stats.MtimeNsec) - } - }) - - t.Run("TimeOmit both is no-op", func(t *testing.T) { - err := fs.WriteFile(ctx, "/utimens_noop.txt", []byte("x"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - statsBefore, _ := fs.Stat(ctx, "/utimens_noop.txt") - - err = fs.Utimens(ctx, "/utimens_noop.txt", TimeOmit(), TimeOmit()) - if err != nil { - t.Fatalf("Utimens failed: %v", err) - } - - statsAfter, _ := fs.Stat(ctx, "/utimens_noop.txt") - if statsBefore.Atime != statsAfter.Atime || statsBefore.AtimeNsec != statsAfter.AtimeNsec { - t.Error("Atime changed on double-omit") - } - if statsBefore.Mtime != statsAfter.Mtime || statsBefore.MtimeNsec != statsAfter.MtimeNsec { - t.Error("Mtime changed on double-omit") - } - // ctime should also be unchanged since we short-circuit - if statsBefore.Ctime != statsAfter.Ctime || statsBefore.CtimeNsec != statsAfter.CtimeNsec { - t.Error("Ctime changed on double-omit") - } - }) - - t.Run("TimeNow sets to current time", func(t *testing.T) { - err := fs.WriteFile(ctx, "/utimens_now.txt", []byte("x"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Set atime to old value - err = fs.Utimens(ctx, "/utimens_now.txt", - TimeSet(1000, 0), - TimeSet(1000, 0), - ) - if err != nil { - t.Fatalf("Utimens failed: %v", err) - } - - before := time.Now() - err = fs.Utimens(ctx, "/utimens_now.txt", TimeNow(), TimeNow()) - if err != nil { - t.Fatalf("Utimens failed: %v", err) - } - after := time.Now() - - stats, _ := fs.Stat(ctx, "/utimens_now.txt") - if stats.Atime < before.Unix() || stats.Atime > after.Unix() { - t.Errorf("Atime %d not in range [%d, %d]", stats.Atime, before.Unix(), after.Unix()) - } - if stats.Mtime < before.Unix() || stats.Mtime > after.Unix() { - t.Errorf("Mtime %d not in range [%d, %d]", stats.Mtime, before.Unix(), after.Unix()) - } - }) - - t.Run("Utimens updates ctime", func(t *testing.T) { - err := fs.WriteFile(ctx, "/utimens_ctime.txt", []byte("x"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - before := time.Now() - err = fs.Utimens(ctx, "/utimens_ctime.txt", - TimeSet(5000, 0), - TimeOmit(), - ) - if err != nil { - t.Fatalf("Utimens failed: %v", err) - } - after := time.Now() - - stats, _ := fs.Stat(ctx, "/utimens_ctime.txt") - if stats.Ctime < before.Unix() || stats.Ctime > after.Unix() { - t.Errorf("Ctime %d not in range [%d, %d]", stats.Ctime, before.Unix(), after.Unix()) - } - }) - - t.Run("Utimens on nonexistent path", func(t *testing.T) { - err := fs.Utimens(ctx, "/no_such_file.txt", TimeNow(), TimeNow()) - if err == nil { - t.Fatal("Expected error") - } - if !IsNotExist(err) { - t.Errorf("Expected ENOENT, got: %v", err) - } - }) - - t.Run("TimeChange constructors", func(t *testing.T) { - omit := TimeOmit() - if !omit.IsOmit() || omit.IsNow() || omit.IsSet() { - t.Error("TimeOmit() should be Omit") - } - - now := TimeNow() - if now.IsOmit() || !now.IsNow() || now.IsSet() { - t.Error("TimeNow() should be Now") - } - - set := TimeSet(100, 200) - if set.IsOmit() || set.IsNow() || !set.IsSet() { - t.Error("TimeSet() should be Set") - } - }) -} - -func TestCreateReturnsStats(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - fs := afs.FS - - t.Run("Create returns valid stats", func(t *testing.T) { - before := time.Now() - stats, f, err := fs.Create(ctx, "/created.txt", 0o644) - if err != nil { - t.Fatalf("Create failed: %v", err) - } - defer f.Close() - after := time.Now() - - if stats == nil { - t.Fatal("Stats should not be nil") - } - if !stats.IsRegularFile() { - t.Errorf("Mode = %o, want regular file", stats.Mode) - } - if stats.Permissions() != 0o644 { - t.Errorf("Permissions = %o, want 0644", stats.Permissions()) - } - if stats.Size != 0 { - t.Errorf("Size = %d, want 0", stats.Size) - } - if stats.Ino <= 0 { - t.Errorf("Ino = %d, want > 0", stats.Ino) - } - if stats.Nlink != 1 { - t.Errorf("Nlink = %d, want 1", stats.Nlink) - } - if stats.Ctime < before.Unix() || stats.Ctime > after.Unix() { - t.Errorf("Ctime %d not in range [%d, %d]", stats.Ctime, before.Unix(), after.Unix()) - } - }) - - t.Run("Create stats match subsequent Stat", func(t *testing.T) { - createStats, f, err := fs.Create(ctx, "/stat_match.txt", 0o600) - if err != nil { - t.Fatalf("Create failed: %v", err) - } - f.Close() - - statStats, err := fs.Stat(ctx, "/stat_match.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - - if createStats.Ino != statStats.Ino { - t.Errorf("Ino: Create=%d, Stat=%d", createStats.Ino, statStats.Ino) - } - if createStats.Mode != statStats.Mode { - t.Errorf("Mode: Create=%o, Stat=%o", createStats.Mode, statStats.Mode) - } - if createStats.Size != statStats.Size { - t.Errorf("Size: Create=%d, Stat=%d", createStats.Size, statStats.Size) - } - }) - - t.Run("Create file handle is usable", func(t *testing.T) { - _, f, err := fs.Create(ctx, "/usable.txt", 0o644) - if err != nil { - t.Fatalf("Create failed: %v", err) - } - defer f.Close() - - n, err := f.Pwrite(ctx, []byte("hello"), 0) - if err != nil { - t.Fatalf("Pwrite failed: %v", err) - } - if n != 5 { - t.Errorf("Pwrite n = %d, want 5", n) - } - - buf := make([]byte, 5) - n, err = f.Pread(ctx, buf, 0) - if err != nil { - t.Fatalf("Pread failed: %v", err) - } - if string(buf[:n]) != "hello" { - t.Errorf("Content = %q, want %q", string(buf[:n]), "hello") - } - }) -} - -func setupTestDB(t *testing.T) *AgentFS { - t.Helper() - ctx := context.Background() - tmpDir := t.TempDir() - dbPath := filepath.Join(tmpDir, "test.db") - - afs, err := Open(ctx, AgentFSOptions{Path: dbPath}) - if err != nil { - t.Fatalf("Failed to create test database: %v", err) - } - - return afs -} diff --git a/sdk/go/benchmark_test.go b/sdk/go/benchmark_test.go deleted file mode 100644 index 2f6c6817..00000000 --- a/sdk/go/benchmark_test.go +++ /dev/null @@ -1,793 +0,0 @@ -package agentfs - -import ( - "context" - "fmt" - "io" - "math/rand" - "testing" -) - -// ============================================================================ -// Benchmark Helpers -// ============================================================================ - -func setupBenchmarkDB(b *testing.B) *AgentFS { - b.Helper() - ctx := context.Background() - afs, err := Open(ctx, AgentFSOptions{Path: ":memory:"}) - if err != nil { - b.Fatalf("Failed to open AgentFS: %v", err) - } - return afs -} - -// ============================================================================ -// Filesystem - Sequential I/O -// ============================================================================ - -func BenchmarkSequentialWrite(b *testing.B) { - sizes := []struct { - name string - size int - }{ - {"4KB", 4 * 1024}, - {"64KB", 64 * 1024}, - {"1MB", 1024 * 1024}, - {"16MB", 16 * 1024 * 1024}, - } - - for _, size := range sizes { - b.Run(size.name, func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - data := make([]byte, size.size) - rand.Read(data) - - b.SetBytes(int64(size.size)) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - path := fmt.Sprintf("/file_%d.bin", i) - if err := afs.FS.WriteFile(ctx, path, data, 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - } - }) - } -} - -func BenchmarkSequentialRead(b *testing.B) { - sizes := []struct { - name string - size int - }{ - {"4KB", 4 * 1024}, - {"64KB", 64 * 1024}, - {"1MB", 1024 * 1024}, - {"16MB", 16 * 1024 * 1024}, - } - - for _, size := range sizes { - b.Run(size.name, func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Create test file - data := make([]byte, size.size) - rand.Read(data) - if err := afs.FS.WriteFile(ctx, "/testfile.bin", data, 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - - b.SetBytes(int64(size.size)) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - if _, err := afs.FS.ReadFile(ctx, "/testfile.bin"); err != nil { - b.Fatalf("ReadFile failed: %v", err) - } - } - }) - } -} - -func BenchmarkStreamingWrite(b *testing.B) { - sizes := []struct { - name string - size int - }{ - {"4KB", 4 * 1024}, - {"64KB", 64 * 1024}, - {"1MB", 1024 * 1024}, - } - - for _, size := range sizes { - b.Run(size.name, func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - data := make([]byte, size.size) - rand.Read(data) - - b.SetBytes(int64(size.size)) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - path := fmt.Sprintf("/stream_%d.bin", i) - _, f, err := afs.FS.Create(ctx, path, 0o644) - if err != nil { - b.Fatalf("Create failed: %v", err) - } - - // Write in 4KB chunks like streaming would - chunkSize := 4096 - for offset := 0; offset < len(data); offset += chunkSize { - end := offset + chunkSize - if end > len(data) { - end = len(data) - } - if _, err := f.Write(data[offset:end]); err != nil { - b.Fatalf("Write failed: %v", err) - } - } - f.Close() - } - }) - } -} - -func BenchmarkStreamingRead(b *testing.B) { - sizes := []struct { - name string - size int - }{ - {"4KB", 4 * 1024}, - {"64KB", 64 * 1024}, - {"1MB", 1024 * 1024}, - } - - for _, size := range sizes { - b.Run(size.name, func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Create test file - data := make([]byte, size.size) - rand.Read(data) - if err := afs.FS.WriteFile(ctx, "/testfile.bin", data, 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - - buf := make([]byte, 4096) - b.SetBytes(int64(size.size)) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - f, err := afs.FS.Open(ctx, "/testfile.bin", O_RDONLY) - if err != nil { - b.Fatalf("Open failed: %v", err) - } - - // Read in 4KB chunks - for { - _, err := f.Read(buf) - if err == io.EOF { - break - } - if err != nil { - b.Fatalf("Read failed: %v", err) - } - } - f.Close() - } - }) - } -} - -func BenchmarkStreamingCopy(b *testing.B) { - sizes := []struct { - name string - size int - }{ - {"64KB", 64 * 1024}, - {"1MB", 1024 * 1024}, - } - - for _, size := range sizes { - b.Run(size.name, func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Create source file - data := make([]byte, size.size) - rand.Read(data) - if err := afs.FS.WriteFile(ctx, "/source.bin", data, 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - - b.SetBytes(int64(size.size)) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - src, err := afs.FS.Open(ctx, "/source.bin", O_RDONLY) - if err != nil { - b.Fatalf("Open source failed: %v", err) - } - - dstPath := fmt.Sprintf("/dest_%d.bin", i) - _, dst, err := afs.FS.Create(ctx, dstPath, 0o644) - if err != nil { - src.Close() - b.Fatalf("Create dest failed: %v", err) - } - - if _, err := io.Copy(dst, src); err != nil { - b.Fatalf("Copy failed: %v", err) - } - - src.Close() - dst.Close() - } - }) - } -} - -// ============================================================================ -// Filesystem - Random I/O -// ============================================================================ - -func BenchmarkRandomRead(b *testing.B) { - sizes := []struct { - name string - fileSize int - readSize int - }{ - {"1MB_file_4KB_read", 1024 * 1024, 4096}, - {"16MB_file_4KB_read", 16 * 1024 * 1024, 4096}, - {"16MB_file_64KB_read", 16 * 1024 * 1024, 64 * 1024}, - } - - for _, size := range sizes { - b.Run(size.name, func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Create test file - data := make([]byte, size.fileSize) - rand.Read(data) - if err := afs.FS.WriteFile(ctx, "/testfile.bin", data, 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - - f, err := afs.FS.Open(ctx, "/testfile.bin", O_RDONLY) - if err != nil { - b.Fatalf("Open failed: %v", err) - } - defer f.Close() - - buf := make([]byte, size.readSize) - maxOffset := size.fileSize - size.readSize - - b.SetBytes(int64(size.readSize)) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - offset := int64(rand.Intn(maxOffset)) - if _, err := f.ReadAt(buf, offset); err != nil && err != io.EOF { - b.Fatalf("ReadAt failed: %v", err) - } - } - }) - } -} - -func BenchmarkRandomWrite(b *testing.B) { - sizes := []struct { - name string - fileSize int - writeSize int - }{ - {"1MB_file_4KB_write", 1024 * 1024, 4096}, - {"16MB_file_4KB_write", 16 * 1024 * 1024, 4096}, - {"16MB_file_64KB_write", 16 * 1024 * 1024, 64 * 1024}, - } - - for _, size := range sizes { - b.Run(size.name, func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Create test file - data := make([]byte, size.fileSize) - if err := afs.FS.WriteFile(ctx, "/testfile.bin", data, 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - - f, err := afs.FS.Open(ctx, "/testfile.bin", O_RDWR) - if err != nil { - b.Fatalf("Open failed: %v", err) - } - defer f.Close() - - writeData := make([]byte, size.writeSize) - rand.Read(writeData) - maxOffset := size.fileSize - size.writeSize - - b.SetBytes(int64(size.writeSize)) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - offset := int64(rand.Intn(maxOffset)) - if _, err := f.WriteAt(writeData, offset); err != nil { - b.Fatalf("WriteAt failed: %v", err) - } - } - }) - } -} - -// ============================================================================ -// Filesystem - Metadata Operations -// ============================================================================ - -func BenchmarkStat(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Create test file - if err := afs.FS.WriteFile(ctx, "/testfile.txt", []byte("hello"), 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - if _, err := afs.FS.Stat(ctx, "/testfile.txt"); err != nil { - b.Fatalf("Stat failed: %v", err) - } - } -} - -func BenchmarkReaddir(b *testing.B) { - counts := []int{10, 100, 1000} - - for _, count := range counts { - b.Run(fmt.Sprintf("%d_entries", count), func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Create files - for i := 0; i < count; i++ { - path := fmt.Sprintf("/file_%04d.txt", i) - if err := afs.FS.WriteFile(ctx, path, []byte("x"), 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - if _, err := afs.FS.Readdir(ctx, "/"); err != nil { - b.Fatalf("Readdir failed: %v", err) - } - } - }) - } -} - -func BenchmarkReaddirPlus(b *testing.B) { - counts := []int{10, 100, 1000} - - for _, count := range counts { - b.Run(fmt.Sprintf("%d_entries", count), func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Create files - for i := 0; i < count; i++ { - path := fmt.Sprintf("/file_%04d.txt", i) - if err := afs.FS.WriteFile(ctx, path, []byte("x"), 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - if _, err := afs.FS.ReaddirPlus(ctx, "/"); err != nil { - b.Fatalf("ReaddirPlus failed: %v", err) - } - } - }) - } -} - -func BenchmarkMkdir(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - path := fmt.Sprintf("/dir_%d", i) - if err := afs.FS.Mkdir(ctx, path, 0o755); err != nil { - b.Fatalf("Mkdir failed: %v", err) - } - } -} - -func BenchmarkMkdirAll(b *testing.B) { - depths := []int{2, 5, 10} - - for _, depth := range depths { - b.Run(fmt.Sprintf("depth_%d", depth), func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - path := fmt.Sprintf("/root_%d", i) - for d := 0; d < depth; d++ { - path += fmt.Sprintf("/level_%d", d) - } - if err := afs.FS.MkdirAll(ctx, path, 0o755); err != nil { - b.Fatalf("MkdirAll failed: %v", err) - } - } - }) - } -} - -func BenchmarkCreateDelete(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - data := []byte("small file content") - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - path := fmt.Sprintf("/file_%d.txt", i) - if err := afs.FS.WriteFile(ctx, path, data, 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - if err := afs.FS.Unlink(ctx, path); err != nil { - b.Fatalf("Unlink failed: %v", err) - } - } -} - -func BenchmarkRename(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Create initial file - if err := afs.FS.WriteFile(ctx, "/file.txt", []byte("content"), 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - oldPath := "/file.txt" - newPath := "/renamed.txt" - if i%2 == 1 { - oldPath, newPath = newPath, oldPath - } - if err := afs.FS.Rename(ctx, oldPath, newPath); err != nil { - b.Fatalf("Rename failed: %v", err) - } - } -} - -func BenchmarkChmod(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - if err := afs.FS.WriteFile(ctx, "/file.txt", []byte("content"), 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - mode := int64(0o644) - if i%2 == 1 { - mode = 0o755 - } - if err := afs.FS.Chmod(ctx, "/file.txt", mode); err != nil { - b.Fatalf("Chmod failed: %v", err) - } - } -} - -// ============================================================================ -// Filesystem - Path Resolution -// ============================================================================ - -func BenchmarkPathResolution(b *testing.B) { - depths := []int{1, 5, 10, 20} - - for _, depth := range depths { - b.Run(fmt.Sprintf("depth_%d", depth), func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Create deep directory structure - path := "" - for d := 0; d < depth; d++ { - path += fmt.Sprintf("/dir_%d", d) - } - if err := afs.FS.MkdirAll(ctx, path, 0o755); err != nil { - b.Fatalf("MkdirAll failed: %v", err) - } - - // Create file at the deepest level - filePath := path + "/file.txt" - if err := afs.FS.WriteFile(ctx, filePath, []byte("deep"), 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - if _, err := afs.FS.Stat(ctx, filePath); err != nil { - b.Fatalf("Stat failed: %v", err) - } - } - }) - } -} - -// ============================================================================ -// KV Store -// ============================================================================ - -func BenchmarkKVSet(b *testing.B) { - sizes := []struct { - name string - value any - }{ - {"small_string", "hello world"}, - {"medium_string", string(make([]byte, 1024))}, - {"large_string", string(make([]byte, 64*1024))}, - {"small_struct", struct { - Name string `json:"name"` - Age int `json:"age"` - }{"Alice", 30}}, - {"large_struct", struct { - Data [100]int `json:"data"` - }{}}, - } - - for _, size := range sizes { - b.Run(size.name, func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - key := fmt.Sprintf("key_%d", i) - if err := afs.KV.Set(ctx, key, size.value); err != nil { - b.Fatalf("Set failed: %v", err) - } - } - }) - } -} - -func BenchmarkKVGet(b *testing.B) { - sizes := []struct { - name string - value any - }{ - {"small_string", "hello world"}, - {"medium_string", string(make([]byte, 1024))}, - {"large_string", string(make([]byte, 64*1024))}, - } - - for _, size := range sizes { - b.Run(size.name, func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Set initial value - if err := afs.KV.Set(ctx, "testkey", size.value); err != nil { - b.Fatalf("Set failed: %v", err) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - var result string - if err := afs.KV.Get(ctx, "testkey", &result); err != nil { - b.Fatalf("Get failed: %v", err) - } - } - }) - } -} - -func BenchmarkKVGetGeneric(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - type Config struct { - Name string `json:"name"` - Version int `json:"version"` - Debug bool `json:"debug"` - } - - cfg := Config{Name: "myapp", Version: 1, Debug: true} - if err := afs.KV.Set(ctx, "config", cfg); err != nil { - b.Fatalf("Set failed: %v", err) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - if _, err := KVGet[Config](ctx, afs.KV, "config"); err != nil { - b.Fatalf("KVGet failed: %v", err) - } - } -} - -func BenchmarkKVHas(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - if err := afs.KV.Set(ctx, "exists", "value"); err != nil { - b.Fatalf("Set failed: %v", err) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - if _, err := afs.KV.Has(ctx, "exists"); err != nil { - b.Fatalf("Has failed: %v", err) - } - } -} - -func BenchmarkKVKeys(b *testing.B) { - counts := []int{10, 100, 1000} - - for _, count := range counts { - b.Run(fmt.Sprintf("%d_keys", count), func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Create keys - for i := 0; i < count; i++ { - key := fmt.Sprintf("prefix:key_%04d", i) - if err := afs.KV.Set(ctx, key, i); err != nil { - b.Fatalf("Set failed: %v", err) - } - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - if _, err := afs.KV.Keys(ctx, "prefix:"); err != nil { - b.Fatalf("Keys failed: %v", err) - } - } - }) - } -} - -func BenchmarkKVList(b *testing.B) { - counts := []int{10, 100, 1000} - - for _, count := range counts { - b.Run(fmt.Sprintf("%d_keys", count), func(b *testing.B) { - afs := setupBenchmarkDB(b) - defer afs.Close() - ctx := context.Background() - - // Create keys - for i := 0; i < count; i++ { - key := fmt.Sprintf("prefix:key_%04d", i) - if err := afs.KV.Set(ctx, key, i); err != nil { - b.Fatalf("Set failed: %v", err) - } - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - if _, err := afs.KV.List(ctx, "prefix:"); err != nil { - b.Fatalf("List failed: %v", err) - } - } - }) - } -} - -// ============================================================================ -// Chunk Size Comparison -// ============================================================================ - -func BenchmarkChunkSize(b *testing.B) { - chunkSizes := []int{4096, 16384, 65536} - fileSize := 1024 * 1024 // 1MB - - for _, chunkSize := range chunkSizes { - b.Run(fmt.Sprintf("chunk_%dKB_write", chunkSize/1024), func(b *testing.B) { - ctx := context.Background() - afs, err := Open(ctx, AgentFSOptions{ - Path: ":memory:", - ChunkSize: chunkSize, - }) - if err != nil { - b.Fatalf("Failed to open AgentFS: %v", err) - } - defer afs.Close() - - data := make([]byte, fileSize) - rand.Read(data) - - b.SetBytes(int64(fileSize)) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - path := fmt.Sprintf("/file_%d.bin", i) - if err := afs.FS.WriteFile(ctx, path, data, 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - } - }) - - b.Run(fmt.Sprintf("chunk_%dKB_read", chunkSize/1024), func(b *testing.B) { - ctx := context.Background() - afs, err := Open(ctx, AgentFSOptions{ - Path: ":memory:", - ChunkSize: chunkSize, - }) - if err != nil { - b.Fatalf("Failed to open AgentFS: %v", err) - } - defer afs.Close() - - data := make([]byte, fileSize) - rand.Read(data) - if err := afs.FS.WriteFile(ctx, "/testfile.bin", data, 0o644); err != nil { - b.Fatalf("WriteFile failed: %v", err) - } - - b.SetBytes(int64(fileSize)) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - if _, err := afs.FS.ReadFile(ctx, "/testfile.bin"); err != nil { - b.Fatalf("ReadFile failed: %v", err) - } - } - }) - } -} diff --git a/sdk/go/constants.go b/sdk/go/constants.go deleted file mode 100644 index da498a02..00000000 --- a/sdk/go/constants.go +++ /dev/null @@ -1,78 +0,0 @@ -package agentfs - -// File type constants (upper bits of mode) -const ( - S_IFMT = 0o170000 // File type mask - S_IFREG = 0o100000 // Regular file - S_IFDIR = 0o040000 // Directory - S_IFLNK = 0o120000 // Symbolic link - S_IFIFO = 0o010000 // FIFO/named pipe - S_IFCHR = 0o020000 // Character device - S_IFBLK = 0o060000 // Block device - S_IFSOCK = 0o140000 // Socket -) - -// Permission bits (lower 12 bits of mode) -const ( - S_IRWXU = 0o0700 // Owner read/write/execute - S_IRUSR = 0o0400 // Owner read - S_IWUSR = 0o0200 // Owner write - S_IXUSR = 0o0100 // Owner execute - - S_IRWXG = 0o0070 // Group read/write/execute - S_IRGRP = 0o0040 // Group read - S_IWGRP = 0o0020 // Group write - S_IXGRP = 0o0010 // Group execute - - S_IRWXO = 0o0007 // Others read/write/execute - S_IROTH = 0o0004 // Others read - S_IWOTH = 0o0002 // Others write - S_IXOTH = 0o0001 // Others execute -) - -// Default modes -const ( - DefaultFileMode = S_IFREG | 0o644 // Regular file: rw-r--r-- - DefaultDirMode = S_IFDIR | 0o755 // Directory: rwxr-xr-x -) - -// Default chunk size for file data storage -const DefaultChunkSize = 4096 - -// POSIX error codes -const ( - EPERM = 1 // Operation not permitted - ENOENT = 2 // No such file or directory - EIO = 5 // I/O error - EBADF = 9 // Bad file descriptor - EACCES = 13 // Permission denied - EEXIST = 17 // File exists - ENOTDIR = 20 // Not a directory - EISDIR = 21 // Is a directory - EINVAL = 22 // Invalid argument - ENOSPC = 28 // No space left on device - ENAMETOOLONG = 36 // File name too long - ENOSYS = 38 // Function not implemented - ENOTEMPTY = 39 // Directory not empty - ELOOP = 40 // Too many symbolic links -) - -// Filesystem limits -const ( - MaxNameLen = 255 // Maximum filename length in bytes - MaxSymlinkDepth = 40 // Maximum number of symlinks followed during path resolution -) - -// Open flags (matching os package) -const ( - O_RDONLY = 0x0 - O_WRONLY = 0x1 - O_RDWR = 0x2 - O_APPEND = 0x400 - O_CREATE = 0x40 - O_EXCL = 0x80 - O_TRUNC = 0x200 -) - -// Root inode number -const RootIno = 1 diff --git a/sdk/go/errors.go b/sdk/go/errors.go deleted file mode 100644 index adbec3b6..00000000 --- a/sdk/go/errors.go +++ /dev/null @@ -1,189 +0,0 @@ -package agentfs - -import ( - "errors" - "fmt" -) - -// FSError represents a filesystem error with POSIX semantics -type FSError struct { - Code int // POSIX error code - Syscall string // Operation that failed (e.g., "stat", "open", "unlink") - Path string // Path that caused the error - Message string // Human-readable error message -} - -// Error implements the error interface -func (e *FSError) Error() string { - if e.Message != "" { - return fmt.Sprintf("%s %s: %s", e.Syscall, e.Path, e.Message) - } - return fmt.Sprintf("%s %s: %s", e.Syscall, e.Path, e.codeMessage()) -} - -// codeMessage returns a human-readable message for the error code -func (e *FSError) codeMessage() string { - switch e.Code { - case EPERM: - return "operation not permitted" - case ENOENT: - return "no such file or directory" - case EIO: - return "input/output error" - case EBADF: - return "bad file descriptor" - case EACCES: - return "permission denied" - case EEXIST: - return "file exists" - case ENOTDIR: - return "not a directory" - case EISDIR: - return "is a directory" - case EINVAL: - return "invalid argument" - case ENOSPC: - return "no space left on device" - case ENOSYS: - return "function not implemented" - case ENOTEMPTY: - return "directory not empty" - case ENAMETOOLONG: - return "file name too long" - case ELOOP: - return "too many levels of symbolic links" - default: - return fmt.Sprintf("error code %d", e.Code) - } -} - -// Is implements errors.Is for FSError -func (e *FSError) Is(target error) bool { - var fsErr *FSError - if errors.As(target, &fsErr) { - return e.Code == fsErr.Code - } - return false -} - -// NewFSError creates a new FSError -func NewFSError(code int, syscall, path, message string) *FSError { - return &FSError{ - Code: code, - Syscall: syscall, - Path: path, - Message: message, - } -} - -// Convenience constructors for common errors - -// ErrNoent returns an ENOENT error (no such file or directory) -func ErrNoent(syscall, path string) *FSError { - return &FSError{Code: ENOENT, Syscall: syscall, Path: path} -} - -// ErrExist returns an EEXIST error (file exists) -func ErrExist(syscall, path string) *FSError { - return &FSError{Code: EEXIST, Syscall: syscall, Path: path} -} - -// ErrIsDir returns an EISDIR error (is a directory) -func ErrIsDir(syscall, path string) *FSError { - return &FSError{Code: EISDIR, Syscall: syscall, Path: path} -} - -// ErrNotDir returns an ENOTDIR error (not a directory) -func ErrNotDir(syscall, path string) *FSError { - return &FSError{Code: ENOTDIR, Syscall: syscall, Path: path} -} - -// ErrNotEmpty returns an ENOTEMPTY error (directory not empty) -func ErrNotEmpty(syscall, path string) *FSError { - return &FSError{Code: ENOTEMPTY, Syscall: syscall, Path: path} -} - -// ErrInval returns an EINVAL error (invalid argument) -func ErrInval(syscall, path, message string) *FSError { - return &FSError{Code: EINVAL, Syscall: syscall, Path: path, Message: message} -} - -// ErrPerm returns an EPERM error (operation not permitted) -func ErrPerm(syscall, path string) *FSError { - return &FSError{Code: EPERM, Syscall: syscall, Path: path} -} - -// ErrNosys returns an ENOSYS error (function not implemented) -func ErrNosys(syscall, path string) *FSError { - return &FSError{Code: ENOSYS, Syscall: syscall, Path: path} -} - -// ErrLoop returns an ELOOP error (too many symbolic links) -func ErrLoop(syscall, path string) *FSError { - return &FSError{Code: ELOOP, Syscall: syscall, Path: path} -} - -// ErrNameTooLong returns an ENAMETOOLONG error (file name too long) -func ErrNameTooLong(syscall, path string) *FSError { - return &FSError{Code: ENAMETOOLONG, Syscall: syscall, Path: path} -} - -// ErrRootOperation returns an EPERM error for operations that cannot modify root -func ErrRootOperation(syscall, path string) *FSError { - return &FSError{Code: EPERM, Syscall: syscall, Path: path, Message: "cannot modify root directory"} -} - -// ErrInvalidRename returns an EINVAL error for invalid rename operations -func ErrInvalidRename(syscall, path string) *FSError { - return &FSError{Code: EINVAL, Syscall: syscall, Path: path, Message: "cannot rename directory into its own subtree"} -} - -// ErrNotSymlink returns an EINVAL error when a symlink was expected -func ErrNotSymlink(syscall, path string) *FSError { - return &FSError{Code: EINVAL, Syscall: syscall, Path: path, Message: "not a symbolic link"} -} - -// IsNameTooLong returns true if the error indicates a filename was too long -func IsNameTooLong(err error) bool { - var fsErr *FSError - if errors.As(err, &fsErr) { - return fsErr.Code == ENAMETOOLONG - } - return false -} - -// IsNotExist returns true if the error indicates the file does not exist -func IsNotExist(err error) bool { - var fsErr *FSError - if errors.As(err, &fsErr) { - return fsErr.Code == ENOENT - } - return false -} - -// IsExist returns true if the error indicates the file already exists -func IsExist(err error) bool { - var fsErr *FSError - if errors.As(err, &fsErr) { - return fsErr.Code == EEXIST - } - return false -} - -// IsLoop returns true if the error indicates too many symbolic links were encountered -func IsLoop(err error) bool { - var fsErr *FSError - if errors.As(err, &fsErr) { - return fsErr.Code == ELOOP - } - return false -} - -// IsPermission returns true if the error indicates a permission problem -func IsPermission(err error) bool { - var fsErr *FSError - if errors.As(err, &fsErr) { - return fsErr.Code == EPERM || fsErr.Code == EACCES - } - return false -} diff --git a/sdk/go/file.go b/sdk/go/file.go deleted file mode 100644 index de8738a1..00000000 --- a/sdk/go/file.go +++ /dev/null @@ -1,430 +0,0 @@ -package agentfs - -import ( - "context" - "io" - "time" -) - -// File represents an open file handle for read/write operations. -// -// File implements the following io interfaces: -// - io.Reader, io.Writer (sequential I/O with position tracking) -// - io.ReaderAt, io.WriterAt (random access I/O) -// - io.Seeker (position control) -// - io.Closer -// -// For context-aware operations, use Pread/Pwrite or set the context via WithContext. -type File struct { - fs *Filesystem - ino int64 - path string - flags int - offset int64 // Current file position for Read/Write - ctx context.Context // Context for streaming operations -} - -// Compile-time interface checks -var ( - _ io.Reader = (*File)(nil) - _ io.Writer = (*File)(nil) - _ io.Seeker = (*File)(nil) - _ io.Closer = (*File)(nil) - _ io.ReaderAt = (*File)(nil) - _ io.WriterAt = (*File)(nil) - _ io.ReadSeeker = (*File)(nil) - _ io.WriteSeeker = (*File)(nil) -) - -// WithContext returns a copy of the File with the given context. -// This context will be used for Read, Write, and Seek operations. -func (f *File) WithContext(ctx context.Context) *File { - return &File{ - fs: f.fs, - ino: f.ino, - path: f.path, - flags: f.flags, - offset: f.offset, - ctx: ctx, - } -} - -// context returns the file's context, defaulting to Background if not set. -func (f *File) context() context.Context { - if f.ctx != nil { - return f.ctx - } - return context.Background() -} - -// Read reads up to len(p) bytes into p, advancing the file offset. -// It implements io.Reader. -// -// Read returns io.EOF when the end of file is reached. -func (f *File) Read(p []byte) (int, error) { - n, err := f.Pread(f.context(), p, f.offset) - f.offset += int64(n) - - // Convert zero-read at EOF to io.EOF - if n == 0 && err == nil { - return 0, io.EOF - } - return n, err -} - -// Write writes len(p) bytes from p, advancing the file offset. -// It implements io.Writer. -func (f *File) Write(p []byte) (int, error) { - n, err := f.Pwrite(f.context(), p, f.offset) - f.offset += int64(n) - return n, err -} - -// Seek sets the offset for the next Read or Write. -// It implements io.Seeker. -// -// Whence values: -// - io.SeekStart (0): offset is relative to the start of the file -// - io.SeekCurrent (1): offset is relative to the current position -// - io.SeekEnd (2): offset is relative to the end of the file -func (f *File) Seek(offset int64, whence int) (int64, error) { - var newOffset int64 - - switch whence { - case io.SeekStart: - newOffset = offset - case io.SeekCurrent: - newOffset = f.offset + offset - case io.SeekEnd: - stats, err := f.fs.statInode(f.context(), f.ino) - if err != nil { - return 0, err - } - newOffset = stats.Size + offset - default: - return 0, ErrInval("seek", f.path, "invalid whence") - } - - if newOffset < 0 { - return 0, ErrInval("seek", f.path, "negative offset") - } - - f.offset = newOffset - return newOffset, nil -} - -// ReadAt reads len(p) bytes at the given offset. -// It implements io.ReaderAt. -// -// ReadAt does not affect the file's current offset. -func (f *File) ReadAt(p []byte, off int64) (int, error) { - n, err := f.Pread(f.context(), p, off) - - // io.ReaderAt requires returning io.EOF if fewer bytes were read - if n < len(p) && err == nil { - err = io.EOF - } - return n, err -} - -// WriteAt writes len(p) bytes at the given offset. -// It implements io.WriterAt. -// -// WriteAt does not affect the file's current offset. -func (f *File) WriteAt(p []byte, off int64) (int, error) { - return f.Pwrite(f.context(), p, off) -} - -// Offset returns the current file offset. -func (f *File) Offset() int64 { - return f.offset -} - -// Pread reads data at a specific offset (positioned read). -// Returns the number of bytes read and any error. -// -// Unlike Read, Pread does not modify the file's current offset. -func (f *File) Pread(ctx context.Context, buf []byte, offset int64) (int, error) { - if len(buf) == 0 { - return 0, nil - } - - stats, err := f.fs.statInode(ctx, f.ino) - if err != nil { - return 0, err - } - - // Check bounds - if offset >= stats.Size { - return 0, nil - } - - length := int64(len(buf)) - if offset+length > stats.Size { - length = stats.Size - offset - } - - chunkSize := int64(f.fs.chunkSize) - startChunk := offset / chunkSize - endChunk := (offset + length - 1) / chunkSize - - rows, err := f.fs.db.QueryContext(ctx, queryChunkRange, f.ino, startChunk, endChunk) - if err != nil { - return 0, err - } - defer rows.Close() - - // Collect chunks - chunks := make(map[int64][]byte) - for rows.Next() { - var chunkIndex int64 - var data []byte - if err := rows.Scan(&chunkIndex, &data); err != nil { - return 0, err - } - chunks[chunkIndex] = data - } - if err := rows.Err(); err != nil { - return 0, err - } - - // Extract requested bytes - bytesRead := 0 - currentOffset := offset - - for currentOffset < offset+length { - chunkIndex := currentOffset / chunkSize - offsetInChunk := currentOffset % chunkSize - - chunk, ok := chunks[chunkIndex] - if !ok { - // Sparse file - treat as zeros - chunk = make([]byte, chunkSize) - } - - // Calculate how many bytes to copy from this chunk - bytesFromChunk := chunkSize - offsetInChunk - remaining := offset + length - currentOffset - if bytesFromChunk > remaining { - bytesFromChunk = remaining - } - if offsetInChunk+bytesFromChunk > int64(len(chunk)) { - bytesFromChunk = int64(len(chunk)) - offsetInChunk - } - - copy(buf[bytesRead:], chunk[offsetInChunk:offsetInChunk+bytesFromChunk]) - bytesRead += int(bytesFromChunk) - currentOffset += bytesFromChunk - } - - // Update atime - now := time.Now() - f.fs.db.ExecContext(ctx, updateInodeAtime, now.Unix(), int64(now.Nanosecond()), f.ino) - - return bytesRead, nil -} - -// Pwrite writes data at a specific offset (positioned write). -// Returns the number of bytes written and any error. -// -// Unlike Write, Pwrite does not modify the file's current offset. -func (f *File) Pwrite(ctx context.Context, data []byte, offset int64) (int, error) { - if len(data) == 0 { - return 0, nil - } - - stats, err := f.fs.statInode(ctx, f.ino) - if err != nil { - return 0, err - } - - chunkSize := int64(f.fs.chunkSize) - endOffset := offset + int64(len(data)) - - // Calculate affected chunks - startChunk := offset / chunkSize - endChunk := (endOffset - 1) / chunkSize - - // Read existing chunks that we'll partially overwrite - existingChunks := make(map[int64][]byte) - - rows, err := f.fs.db.QueryContext(ctx, queryChunkRange, f.ino, startChunk, endChunk) - if err != nil { - return 0, err - } - for rows.Next() { - var chunkIndex int64 - var chunkData []byte - if err := rows.Scan(&chunkIndex, &chunkData); err != nil { - rows.Close() - return 0, err - } - existingChunks[chunkIndex] = chunkData - } - rows.Close() - - // Write data - bytesWritten := 0 - currentOffset := offset - dataOffset := 0 - - for currentOffset < endOffset { - chunkIndex := currentOffset / chunkSize - offsetInChunk := currentOffset % chunkSize - - // Get or create chunk buffer - chunk, exists := existingChunks[chunkIndex] - if !exists { - chunk = make([]byte, 0) - } - - // Extend chunk if needed - requiredLen := offsetInChunk + int64(len(data)-dataOffset) - if requiredLen > chunkSize { - requiredLen = chunkSize - } - if int64(len(chunk)) < requiredLen { - newChunk := make([]byte, requiredLen) - copy(newChunk, chunk) - chunk = newChunk - } - - // Calculate how many bytes to write to this chunk - bytesToWrite := chunkSize - offsetInChunk - remainingData := int64(len(data) - dataOffset) - if bytesToWrite > remainingData { - bytesToWrite = remainingData - } - - // Write to chunk - copy(chunk[offsetInChunk:], data[dataOffset:dataOffset+int(bytesToWrite)]) - - // Store chunk - if _, err := f.fs.db.ExecContext(ctx, insertChunk, f.ino, chunkIndex, chunk); err != nil { - return bytesWritten, err - } - - bytesWritten += int(bytesToWrite) - currentOffset += bytesToWrite - dataOffset += int(bytesToWrite) - } - - // Update inode size if we extended the file - newSize := stats.Size - if endOffset > stats.Size { - newSize = endOffset - } - - now := time.Now() - if _, err := f.fs.db.ExecContext(ctx, updateInodeSize, newSize, now.Unix(), int64(now.Nanosecond()), f.ino); err != nil { - return bytesWritten, err - } - - return bytesWritten, nil -} - -// Truncate sets the file size. -func (f *File) Truncate(ctx context.Context, size int64) error { - stats, err := f.fs.statInode(ctx, f.ino) - if err != nil { - return err - } - - chunkSize := int64(f.fs.chunkSize) - - if size < stats.Size { - // Shrinking: delete chunks beyond new size - lastChunk := size / chunkSize - if size%chunkSize == 0 && size > 0 { - lastChunk-- - } - - // Delete chunks beyond the new last chunk - if _, err := f.fs.db.ExecContext(ctx, deleteChunksFromIndex, f.ino, lastChunk+1); err != nil { - return err - } - - // Truncate the last chunk if needed - if size > 0 { - offsetInLastChunk := size % chunkSize - if offsetInLastChunk > 0 { - // Read the last chunk - var chunk []byte - err := f.fs.db.QueryRowContext(ctx, "SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?", f.ino, lastChunk).Scan(&chunk) - if err == nil && int64(len(chunk)) > offsetInLastChunk { - // Truncate and rewrite - chunk = chunk[:offsetInLastChunk] - if _, err := f.fs.db.ExecContext(ctx, insertChunk, f.ino, lastChunk, chunk); err != nil { - return err - } - } - } - } - } else if size > stats.Size { - // Extending: just update size (sparse file behavior) - // The missing chunks will be treated as zeros on read - } - - now := time.Now() - if _, err := f.fs.db.ExecContext(ctx, updateInodeSize, size, now.Unix(), int64(now.Nanosecond()), f.ino); err != nil { - return err - } - - return nil -} - -// Fsync flushes data to storage. -// Note: This is effectively a no-op for SQLite in WAL mode as writes are already durable. -func (f *File) Fsync(ctx context.Context) error { - // SQLite in WAL mode automatically handles durability - // We could issue a PRAGMA wal_checkpoint here if strict durability is needed - return nil -} - -// Stat returns the file's current metadata. -func (f *File) Stat(ctx context.Context) (*Stats, error) { - return f.fs.statInode(ctx, f.ino) -} - -// Path returns the file's path. -func (f *File) Path() string { - return f.path -} - -// Size returns the current file size. -func (f *File) Size() (int64, error) { - stats, err := f.fs.statInode(f.context(), f.ino) - if err != nil { - return 0, err - } - return stats.Size, nil -} - -// Close closes the file handle. -func (f *File) Close() error { - // Currently no resources to release - // In the future, this could handle things like advisory locks - return nil -} - -// Reader returns an io.Reader that reads from the current offset. -// This is useful when you need to pass the file to functions expecting io.Reader. -func (f *File) Reader() io.Reader { - return f -} - -// Writer returns an io.Writer that writes at the current offset. -// This is useful when you need to pass the file to functions expecting io.Writer. -func (f *File) Writer() io.Writer { - return f -} - -// ReadSeeker returns an io.ReadSeeker interface. -func (f *File) ReadSeeker() io.ReadSeeker { - return f -} - -// WriteSeeker returns an io.WriteSeeker interface. -func (f *File) WriteSeeker() io.WriteSeeker { - return f -} diff --git a/sdk/go/file_streaming_test.go b/sdk/go/file_streaming_test.go deleted file mode 100644 index 830eb8cd..00000000 --- a/sdk/go/file_streaming_test.go +++ /dev/null @@ -1,590 +0,0 @@ -package agentfs - -import ( - "bytes" - "context" - "io" - "testing" -) - -func TestFile_IOReader(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - content := []byte("Hello, World! This is a test file for streaming I/O.") - afs.FS.WriteFile(ctx, "/stream.txt", content, 0o644) - - t.Run("Read entire file", func(t *testing.T) { - f, err := afs.FS.Open(ctx, "/stream.txt", O_RDONLY) - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer f.Close() - - data, err := io.ReadAll(f) - if err != nil { - t.Fatalf("ReadAll failed: %v", err) - } - if string(data) != string(content) { - t.Errorf("Content = %q, want %q", data, content) - } - }) - - t.Run("Read in chunks", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/stream.txt", O_RDONLY) - defer f.Close() - - buf := make([]byte, 10) - var result []byte - - for { - n, err := f.Read(buf) - result = append(result, buf[:n]...) - if err == io.EOF { - break - } - if err != nil { - t.Fatalf("Read failed: %v", err) - } - } - - if string(result) != string(content) { - t.Errorf("Content = %q, want %q", result, content) - } - }) - - t.Run("Read returns EOF at end", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/stream.txt", O_RDONLY) - defer f.Close() - - // Read entire file - io.ReadAll(f) - - // Next read should return EOF - buf := make([]byte, 10) - n, err := f.Read(buf) - if n != 0 { - t.Errorf("n = %d, want 0", n) - } - if err != io.EOF { - t.Errorf("err = %v, want io.EOF", err) - } - }) - - t.Run("Offset advances", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/stream.txt", O_RDONLY) - defer f.Close() - - if f.Offset() != 0 { - t.Errorf("Initial offset = %d, want 0", f.Offset()) - } - - buf := make([]byte, 5) - f.Read(buf) - - if f.Offset() != 5 { - t.Errorf("Offset after read = %d, want 5", f.Offset()) - } - }) -} - -func TestFile_IOWriter(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - t.Run("Write entire file", func(t *testing.T) { - _, f, err := afs.FS.Create(ctx, "/write.txt", 0o644) - if err != nil { - t.Fatalf("Create failed: %v", err) - } - - content := []byte("Hello from streaming write!") - n, err := f.Write(content) - if err != nil { - t.Fatalf("Write failed: %v", err) - } - if n != len(content) { - t.Errorf("n = %d, want %d", n, len(content)) - } - f.Close() - - // Verify - data, _ := afs.FS.ReadFile(ctx, "/write.txt") - if string(data) != string(content) { - t.Errorf("Content = %q, want %q", data, content) - } - }) - - t.Run("Write in chunks", func(t *testing.T) { - _, f, _ := afs.FS.Create(ctx, "/chunks.txt", 0o644) - defer f.Close() - - chunks := []string{"Hello, ", "World! ", "This is ", "chunked."} - for _, chunk := range chunks { - f.Write([]byte(chunk)) - } - - data, _ := afs.FS.ReadFile(ctx, "/chunks.txt") - expected := "Hello, World! This is chunked." - if string(data) != expected { - t.Errorf("Content = %q, want %q", data, expected) - } - }) - - t.Run("Offset advances on write", func(t *testing.T) { - _, f, _ := afs.FS.Create(ctx, "/offset.txt", 0o644) - defer f.Close() - - if f.Offset() != 0 { - t.Errorf("Initial offset = %d, want 0", f.Offset()) - } - - f.Write([]byte("12345")) - if f.Offset() != 5 { - t.Errorf("Offset after write = %d, want 5", f.Offset()) - } - - f.Write([]byte("67890")) - if f.Offset() != 10 { - t.Errorf("Offset after second write = %d, want 10", f.Offset()) - } - }) -} - -func TestFile_IOSeeker(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - content := []byte("0123456789ABCDEF") - afs.FS.WriteFile(ctx, "/seek.txt", content, 0o644) - - t.Run("SeekStart", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/seek.txt", O_RDONLY) - defer f.Close() - - pos, err := f.Seek(5, io.SeekStart) - if err != nil { - t.Fatalf("Seek failed: %v", err) - } - if pos != 5 { - t.Errorf("pos = %d, want 5", pos) - } - - buf := make([]byte, 5) - f.Read(buf) - if string(buf) != "56789" { - t.Errorf("Read = %q, want %q", buf, "56789") - } - }) - - t.Run("SeekCurrent", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/seek.txt", O_RDONLY) - defer f.Close() - - f.Seek(5, io.SeekStart) - pos, err := f.Seek(3, io.SeekCurrent) - if err != nil { - t.Fatalf("Seek failed: %v", err) - } - if pos != 8 { - t.Errorf("pos = %d, want 8", pos) - } - - // Negative seek - pos, _ = f.Seek(-2, io.SeekCurrent) - if pos != 6 { - t.Errorf("pos = %d, want 6", pos) - } - }) - - t.Run("SeekEnd", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/seek.txt", O_RDONLY) - defer f.Close() - - pos, err := f.Seek(-5, io.SeekEnd) - if err != nil { - t.Fatalf("Seek failed: %v", err) - } - if pos != 11 { - t.Errorf("pos = %d, want 11", pos) - } - - buf := make([]byte, 5) - f.Read(buf) - if string(buf) != "BCDEF" { - t.Errorf("Read = %q, want %q", buf, "BCDEF") - } - }) - - t.Run("Seek to beginning", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/seek.txt", O_RDONLY) - defer f.Close() - - // Read some data - buf := make([]byte, 10) - f.Read(buf) - - // Seek back to start - pos, _ := f.Seek(0, io.SeekStart) - if pos != 0 { - t.Errorf("pos = %d, want 0", pos) - } - - // Read from beginning - buf = make([]byte, 5) - f.Read(buf) - if string(buf) != "01234" { - t.Errorf("Read = %q, want %q", buf, "01234") - } - }) - - t.Run("Negative offset error", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/seek.txt", O_RDONLY) - defer f.Close() - - _, err := f.Seek(-10, io.SeekStart) - if err == nil { - t.Error("Expected error for negative offset") - } - }) - - t.Run("Invalid whence error", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/seek.txt", O_RDONLY) - defer f.Close() - - _, err := f.Seek(0, 99) - if err == nil { - t.Error("Expected error for invalid whence") - } - }) -} - -func TestFile_IOReaderAt(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - content := []byte("0123456789ABCDEF") - afs.FS.WriteFile(ctx, "/readat.txt", content, 0o644) - - t.Run("ReadAt various offsets", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/readat.txt", O_RDONLY) - defer f.Close() - - tests := []struct { - offset int64 - len int - want string - }{ - {0, 5, "01234"}, - {5, 5, "56789"}, - {10, 6, "ABCDEF"}, - {0, 16, "0123456789ABCDEF"}, - } - - for _, tc := range tests { - buf := make([]byte, tc.len) - n, err := f.ReadAt(buf, tc.offset) - if err != nil && err != io.EOF { - t.Errorf("ReadAt(%d) failed: %v", tc.offset, err) - } - if string(buf[:n]) != tc.want { - t.Errorf("ReadAt(%d) = %q, want %q", tc.offset, buf[:n], tc.want) - } - } - }) - - t.Run("ReadAt does not affect offset", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/readat.txt", O_RDONLY) - defer f.Close() - - initialOffset := f.Offset() - - buf := make([]byte, 5) - f.ReadAt(buf, 10) - - if f.Offset() != initialOffset { - t.Errorf("Offset changed from %d to %d", initialOffset, f.Offset()) - } - }) - - t.Run("ReadAt returns EOF for short read", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/readat.txt", O_RDONLY) - defer f.Close() - - buf := make([]byte, 10) - n, err := f.ReadAt(buf, 12) // Only 4 bytes available - - if n != 4 { - t.Errorf("n = %d, want 4", n) - } - if err != io.EOF { - t.Errorf("err = %v, want io.EOF", err) - } - }) -} - -func TestFile_IOWriterAt(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - t.Run("WriteAt various offsets", func(t *testing.T) { - _, f, _ := afs.FS.Create(ctx, "/writeat.txt", 0o644) - defer f.Close() - - // Write "HELLO" at offset 0 - f.WriteAt([]byte("HELLO"), 0) - - // Write "WORLD" at offset 10 (creates sparse region) - f.WriteAt([]byte("WORLD"), 10) - - data, _ := afs.FS.ReadFile(ctx, "/writeat.txt") - if len(data) != 15 { - t.Errorf("len = %d, want 15", len(data)) - } - if string(data[:5]) != "HELLO" { - t.Errorf("First 5 bytes = %q, want %q", data[:5], "HELLO") - } - if string(data[10:]) != "WORLD" { - t.Errorf("Last 5 bytes = %q, want %q", data[10:], "WORLD") - } - }) - - t.Run("WriteAt does not affect offset", func(t *testing.T) { - _, f, _ := afs.FS.Create(ctx, "/writeat2.txt", 0o644) - defer f.Close() - - initialOffset := f.Offset() - - f.WriteAt([]byte("test"), 100) - - if f.Offset() != initialOffset { - t.Errorf("Offset changed from %d to %d", initialOffset, f.Offset()) - } - }) -} - -func TestFile_IOCopy(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - content := []byte("Content to be copied using io.Copy") - afs.FS.WriteFile(ctx, "/source.txt", content, 0o644) - - t.Run("io.Copy between files", func(t *testing.T) { - src, _ := afs.FS.Open(ctx, "/source.txt", O_RDONLY) - defer src.Close() - - _, dst, _ := afs.FS.Create(ctx, "/dest.txt", 0o644) - defer dst.Close() - - n, err := io.Copy(dst, src) - if err != nil { - t.Fatalf("io.Copy failed: %v", err) - } - if n != int64(len(content)) { - t.Errorf("Copied %d bytes, want %d", n, len(content)) - } - - // Verify destination - data, _ := afs.FS.ReadFile(ctx, "/dest.txt") - if string(data) != string(content) { - t.Errorf("Dest content = %q, want %q", data, content) - } - }) - - t.Run("io.Copy from bytes.Reader", func(t *testing.T) { - src := bytes.NewReader([]byte("From bytes.Reader")) - - _, dst, _ := afs.FS.Create(ctx, "/from_reader.txt", 0o644) - defer dst.Close() - - io.Copy(dst, src) - - data, _ := afs.FS.ReadFile(ctx, "/from_reader.txt") - if string(data) != "From bytes.Reader" { - t.Errorf("Content = %q", data) - } - }) - - t.Run("io.Copy to bytes.Buffer", func(t *testing.T) { - src, _ := afs.FS.Open(ctx, "/source.txt", O_RDONLY) - defer src.Close() - - var buf bytes.Buffer - io.Copy(&buf, src) - - if buf.String() != string(content) { - t.Errorf("Buffer = %q, want %q", buf.String(), content) - } - }) -} - -func TestFile_LargeFileStreaming(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - // Create a file larger than one chunk (4KB) - size := 50000 - content := make([]byte, size) - for i := range content { - content[i] = byte(i % 256) - } - - t.Run("Stream write large file", func(t *testing.T) { - _, f, _ := afs.FS.Create(ctx, "/large.bin", 0o644) - defer f.Close() - - // Write in small chunks - chunkSize := 1000 - for i := 0; i < size; i += chunkSize { - end := i + chunkSize - if end > size { - end = size - } - n, err := f.Write(content[i:end]) - if err != nil { - t.Fatalf("Write failed at offset %d: %v", i, err) - } - if n != end-i { - t.Errorf("Wrote %d bytes, want %d", n, end-i) - } - } - - // Verify size - fileSize, _ := f.Size() - if fileSize != int64(size) { - t.Errorf("Size = %d, want %d", fileSize, size) - } - }) - - t.Run("Stream read large file", func(t *testing.T) { - afs.FS.WriteFile(ctx, "/large2.bin", content, 0o644) - - f, _ := afs.FS.Open(ctx, "/large2.bin", O_RDONLY) - defer f.Close() - - // Read in small chunks - result := make([]byte, 0, size) - buf := make([]byte, 1000) - - for { - n, err := f.Read(buf) - result = append(result, buf[:n]...) - if err == io.EOF { - break - } - if err != nil { - t.Fatalf("Read failed: %v", err) - } - } - - if len(result) != size { - t.Errorf("Read %d bytes, want %d", len(result), size) - } - - // Verify content - for i := range content { - if result[i] != content[i] { - t.Errorf("Mismatch at byte %d: got %d, want %d", i, result[i], content[i]) - break - } - } - }) -} - -func TestFile_WithContext(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - afs.FS.WriteFile(ctx, "/ctx.txt", []byte("test"), 0o644) - - t.Run("WithContext creates copy", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/ctx.txt", O_RDONLY) - defer f.Close() - - newCtx := context.WithValue(ctx, "key", "value") - f2 := f.WithContext(newCtx) - - if f2 == f { - t.Error("WithContext should return a new file") - } - - // Should still work - buf := make([]byte, 10) - n, _ := f2.Read(buf) - if string(buf[:n]) != "test" { - t.Errorf("Read = %q, want %q", buf[:n], "test") - } - }) - - t.Run("WithContext preserves offset", func(t *testing.T) { - f, _ := afs.FS.Open(ctx, "/ctx.txt", O_RDONLY) - defer f.Close() - - f.Seek(2, io.SeekStart) - - f2 := f.WithContext(ctx) - if f2.Offset() != 2 { - t.Errorf("Offset = %d, want 2", f2.Offset()) - } - }) -} - -func TestFile_ReadWriteInterleaved(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - t.Run("Read then write", func(t *testing.T) { - _, f, _ := afs.FS.Create(ctx, "/rw.txt", 0o644) - defer f.Close() - - // Write initial content - f.Write([]byte("HELLO")) - - // Seek back - f.Seek(0, io.SeekStart) - - // Read - buf := make([]byte, 5) - f.Read(buf) - if string(buf) != "HELLO" { - t.Errorf("Read = %q, want HELLO", buf) - } - - // Write more (appends) - f.Write([]byte("WORLD")) - - // Check total content - data, _ := afs.FS.ReadFile(ctx, "/rw.txt") - if string(data) != "HELLOWORLD" { - t.Errorf("Content = %q, want HELLOWORLD", data) - } - }) - - t.Run("Overwrite in middle", func(t *testing.T) { - afs.FS.WriteFile(ctx, "/overwrite.txt", []byte("AAAAABBBBBCCCCC"), 0o644) - - f, _ := afs.FS.Open(ctx, "/overwrite.txt", O_RDWR) - defer f.Close() - - // Seek to middle - f.Seek(5, io.SeekStart) - - // Overwrite - f.Write([]byte("XXXXX")) - - data, _ := afs.FS.ReadFile(ctx, "/overwrite.txt") - if string(data) != "AAAAAXXXXXCCCCC" { - t.Errorf("Content = %q, want AAAAAXXXXXCCCCC", data) - } - }) -} diff --git a/sdk/go/filesystem.go b/sdk/go/filesystem.go deleted file mode 100644 index 56f1f147..00000000 --- a/sdk/go/filesystem.go +++ /dev/null @@ -1,1132 +0,0 @@ -package agentfs - -import ( - "context" - "database/sql" - "fmt" - "path" - "strings" - "time" -) - -// Filesystem provides POSIX-like file operations backed by SQLite. -type Filesystem struct { - db *sql.DB - chunkSize int -} - -// ChunkSize returns the configured chunk size for file data. -func (fs *Filesystem) ChunkSize() int { - return fs.chunkSize -} - -// Stat returns file/directory metadata for the given path. -// If the path refers to a symlink, Stat follows the symlink and returns -// the target's stats. Use Lstat to get the symlink's own stats. -func (fs *Filesystem) Stat(ctx context.Context, p string) (*Stats, error) { - p = normalizePath(p) - - ino, err := fs.resolvePathFollow(ctx, p, true) - if err != nil { - return nil, err - } - - return fs.statInode(ctx, ino) -} - -// Readdir returns the names of entries in a directory. -func (fs *Filesystem) Readdir(ctx context.Context, p string) ([]string, error) { - p = normalizePath(p) - - ino, err := fs.resolvePathFollow(ctx, p, true) - if err != nil { - return nil, err - } - - // Verify it's a directory - stats, err := fs.statInode(ctx, ino) - if err != nil { - return nil, err - } - if !stats.IsDir() { - return nil, ErrNotDir("readdir", p) - } - - rows, err := fs.db.QueryContext(ctx, queryDentriesByParent, ino) - if err != nil { - return nil, err - } - defer rows.Close() - - var names []string - for rows.Next() { - var name string - if err := rows.Scan(&name); err != nil { - return nil, err - } - names = append(names, name) - } - - return names, rows.Err() -} - -// ReaddirPlus returns directory entries with their stats (optimized batch operation). -func (fs *Filesystem) ReaddirPlus(ctx context.Context, p string) ([]DirEntry, error) { - p = normalizePath(p) - - ino, err := fs.resolvePathFollow(ctx, p, true) - if err != nil { - return nil, err - } - - // Verify it's a directory - stats, err := fs.statInode(ctx, ino) - if err != nil { - return nil, err - } - if !stats.IsDir() { - return nil, ErrNotDir("readdir", p) - } - - rows, err := fs.db.QueryContext(ctx, queryDentriesPlusByParent, ino) - if err != nil { - return nil, err - } - defer rows.Close() - - var entries []DirEntry - for rows.Next() { - var name string - var s Stats - if err := rows.Scan(&name, &s.Ino, &s.Mode, &s.Nlink, &s.UID, &s.GID, &s.Size, &s.Atime, &s.Mtime, &s.Ctime, &s.Rdev, - &s.AtimeNsec, &s.MtimeNsec, &s.CtimeNsec); err != nil { - return nil, err - } - entries = append(entries, DirEntry{Name: name, Stats: &s}) - } - - return entries, rows.Err() -} - -// Mkdir creates a directory. -func (fs *Filesystem) Mkdir(ctx context.Context, p string, mode int64) error { - p = normalizePath(p) - if p == "/" { - return ErrExist("mkdir", p) - } - - parentPath, name := path.Split(p) - parentPath = normalizePath(parentPath) - - if err := validateName("mkdir", name); err != nil { - return err - } - - parentIno, err := fs.resolvePathFollow(ctx, parentPath, true) - if err != nil { - return err - } - - // Check parent is a directory - parentStats, err := fs.statInode(ctx, parentIno) - if err != nil { - return err - } - if !parentStats.IsDir() { - return ErrNotDir("mkdir", parentPath) - } - - // Check if entry already exists - _, err = fs.lookupDentry(ctx, parentIno, name) - if err == nil { - return ErrExist("mkdir", p) - } - - // Create inode - now := time.Now() - nowSec := now.Unix() - nowNsec := int64(now.Nanosecond()) - dirMode := S_IFDIR | (mode & 0o777) - - var ino int64 - err = fs.db.QueryRowContext(ctx, insertInode, dirMode, 0, 0, 0, nowSec, nowSec, nowSec, 0, nowNsec, nowNsec, nowNsec).Scan(&ino) - if err != nil { - return err - } - - // Create dentry - if _, err := fs.db.ExecContext(ctx, insertDentry, name, parentIno, ino); err != nil { - return err - } - - // Increment nlink - if _, err := fs.db.ExecContext(ctx, incrementNlink, ino); err != nil { - return err - } - - return nil -} - -// MkdirAll creates a directory and all parent directories as needed. -func (fs *Filesystem) MkdirAll(ctx context.Context, p string, mode int64) error { - p = normalizePath(p) - if p == "/" { - return nil - } - - components := splitPath(p) - currentPath := "" - - for _, component := range components { - currentPath = currentPath + "/" + component - - stats, err := fs.Stat(ctx, currentPath) - if err != nil { - if !IsNotExist(err) { - return err - } - // Directory doesn't exist, create it - if err := fs.Mkdir(ctx, currentPath, mode); err != nil { - return err - } - } else if !stats.IsDir() { - return ErrNotDir("mkdir", currentPath) - } - } - - return nil -} - -// ReadFile reads the entire contents of a file. -func (fs *Filesystem) ReadFile(ctx context.Context, p string) ([]byte, error) { - p = normalizePath(p) - - ino, err := fs.resolvePathFollow(ctx, p, true) - if err != nil { - return nil, err - } - - stats, err := fs.statInode(ctx, ino) - if err != nil { - return nil, err - } - if stats.IsDir() { - return nil, ErrIsDir("read", p) - } - - // Read all chunks - rows, err := fs.db.QueryContext(ctx, queryChunksByIno, ino) - if err != nil { - return nil, err - } - defer rows.Close() - - var data []byte - for rows.Next() { - var chunk []byte - if err := rows.Scan(&chunk); err != nil { - return nil, err - } - data = append(data, chunk...) - } - if err := rows.Err(); err != nil { - return nil, err - } - - // Update atime - now := time.Now() - fs.db.ExecContext(ctx, updateInodeAtime, now.Unix(), int64(now.Nanosecond()), ino) - - return data, nil -} - -// WriteFile writes data to a file, creating it if it doesn't exist. -func (fs *Filesystem) WriteFile(ctx context.Context, p string, data []byte, mode int64) error { - p = normalizePath(p) - - parentPath, name := path.Split(p) - parentPath = normalizePath(parentPath) - - if err := validateName("write", name); err != nil { - return err - } - - // Ensure parent directory exists - if err := fs.MkdirAll(ctx, parentPath, 0o755); err != nil { - return err - } - - parentIno, err := fs.resolvePathFollow(ctx, parentPath, true) - if err != nil { - return err - } - - now := time.Now() - nowSec := now.Unix() - nowNsec := int64(now.Nanosecond()) - fileMode := S_IFREG | (mode & 0o777) - - // Check if file already exists - existingIno, err := fs.lookupDentry(ctx, parentIno, name) - if err == nil { - // File exists, check it's not a directory - stats, err := fs.statInode(ctx, existingIno) - if err != nil { - return err - } - if stats.IsDir() { - return ErrIsDir("write", p) - } - - // Delete existing data - if _, err := fs.db.ExecContext(ctx, deleteChunksByIno, existingIno); err != nil { - return err - } - - // Write new data - if err := fs.writeChunks(ctx, existingIno, data); err != nil { - return err - } - - // Update inode - if _, err := fs.db.ExecContext(ctx, updateInodeSize, len(data), nowSec, nowNsec, existingIno); err != nil { - return err - } - - return nil - } - - // Create new file - var ino int64 - err = fs.db.QueryRowContext(ctx, insertInode, fileMode, 0, 0, len(data), nowSec, nowSec, nowSec, 0, nowNsec, nowNsec, nowNsec).Scan(&ino) - if err != nil { - return err - } - - // Create dentry - if _, err := fs.db.ExecContext(ctx, insertDentry, name, parentIno, ino); err != nil { - return err - } - - // Increment nlink - if _, err := fs.db.ExecContext(ctx, incrementNlink, ino); err != nil { - return err - } - - // Write data chunks - if err := fs.writeChunks(ctx, ino, data); err != nil { - return err - } - - return nil -} - -// Unlink removes a file. -func (fs *Filesystem) Unlink(ctx context.Context, p string) error { - p = normalizePath(p) - if p == "/" { - return ErrRootOperation("unlink", p) - } - - parentPath, name := path.Split(p) - parentPath = normalizePath(parentPath) - - parentIno, err := fs.resolvePathFollow(ctx, parentPath, true) - if err != nil { - return err - } - - ino, err := fs.lookupDentry(ctx, parentIno, name) - if err != nil { - return ErrNoent("unlink", p) - } - - stats, err := fs.statInode(ctx, ino) - if err != nil { - return err - } - if stats.IsDir() { - return ErrIsDir("unlink", p) - } - - // Delete dentry - if _, err := fs.db.ExecContext(ctx, deleteDentry, parentIno, name); err != nil { - return err - } - - // Decrement nlink - if _, err := fs.db.ExecContext(ctx, decrementNlink, ino); err != nil { - return err - } - - // Check if last link - newStats, err := fs.statInode(ctx, ino) - if err != nil { - return err - } - if newStats.Nlink == 0 { - // Delete inode and data - if _, err := fs.db.ExecContext(ctx, deleteChunksByIno, ino); err != nil { - return err - } - if _, err := fs.db.ExecContext(ctx, deleteSymlink, ino); err != nil { - return err - } - if _, err := fs.db.ExecContext(ctx, deleteInode, ino); err != nil { - return err - } - } - - return nil -} - -// Rmdir removes an empty directory. -func (fs *Filesystem) Rmdir(ctx context.Context, p string) error { - p = normalizePath(p) - if p == "/" { - return ErrRootOperation("rmdir", p) - } - - parentPath, name := path.Split(p) - parentPath = normalizePath(parentPath) - - parentIno, err := fs.resolvePathFollow(ctx, parentPath, true) - if err != nil { - return err - } - - ino, err := fs.lookupDentry(ctx, parentIno, name) - if err != nil { - return ErrNoent("rmdir", p) - } - - stats, err := fs.statInode(ctx, ino) - if err != nil { - return err - } - if !stats.IsDir() { - return ErrNotDir("rmdir", p) - } - - // Check if directory is empty - var count int - if err := fs.db.QueryRowContext(ctx, countDentriesByParent, ino).Scan(&count); err != nil { - return err - } - if count > 0 { - return ErrNotEmpty("rmdir", p) - } - - // Delete dentry - if _, err := fs.db.ExecContext(ctx, deleteDentry, parentIno, name); err != nil { - return err - } - - // Decrement nlink and delete inode - if _, err := fs.db.ExecContext(ctx, decrementNlink, ino); err != nil { - return err - } - if _, err := fs.db.ExecContext(ctx, deleteInode, ino); err != nil { - return err - } - - return nil -} - -// Rename moves or renames a file or directory. -func (fs *Filesystem) Rename(ctx context.Context, oldPath, newPath string) error { - oldPath = normalizePath(oldPath) - newPath = normalizePath(newPath) - - if oldPath == "/" || newPath == "/" { - return ErrRootOperation("rename", oldPath) - } - - oldParentPath, oldName := path.Split(oldPath) - oldParentPath = normalizePath(oldParentPath) - - newParentPath, newName := path.Split(newPath) - newParentPath = normalizePath(newParentPath) - - if err := validateName("rename", newName); err != nil { - return err - } - - // Prevent renaming a directory into its own subtree - if strings.HasPrefix(newPath, oldPath+"/") { - return ErrInvalidRename("rename", oldPath) - } - - oldParentIno, err := fs.resolvePathFollow(ctx, oldParentPath, true) - if err != nil { - return err - } - - ino, err := fs.lookupDentry(ctx, oldParentIno, oldName) - if err != nil { - return ErrNoent("rename", oldPath) - } - - // Ensure new parent exists - if err := fs.MkdirAll(ctx, newParentPath, 0o755); err != nil { - return err - } - - newParentIno, err := fs.resolvePathFollow(ctx, newParentPath, true) - if err != nil { - return err - } - - // Check if destination exists - existingIno, err := fs.lookupDentry(ctx, newParentIno, newName) - if err == nil { - // Destination exists, remove it - existingStats, err := fs.statInode(ctx, existingIno) - if err != nil { - return err - } - - sourceStats, err := fs.statInode(ctx, ino) - if err != nil { - return err - } - - if existingStats.IsDir() && !sourceStats.IsDir() { - return ErrIsDir("rename", newPath) - } - if !existingStats.IsDir() && sourceStats.IsDir() { - return ErrNotDir("rename", newPath) - } - - if existingStats.IsDir() { - if err := fs.Rmdir(ctx, newPath); err != nil { - return err - } - } else { - if err := fs.Unlink(ctx, newPath); err != nil { - return err - } - } - } - - // Update dentry - if _, err := fs.db.ExecContext(ctx, updateDentryParent, newParentIno, newName, oldParentIno, oldName); err != nil { - return err - } - - return nil -} - -// Link creates a hard link. -func (fs *Filesystem) Link(ctx context.Context, existingPath, newPath string) error { - existingPath = normalizePath(existingPath) - newPath = normalizePath(newPath) - - _, newName := path.Split(newPath) - if err := validateName("link", newName); err != nil { - return err - } - - ino, err := fs.resolvePathFollow(ctx, existingPath, true) - if err != nil { - return err - } - - stats, err := fs.statInode(ctx, ino) - if err != nil { - return err - } - if stats.IsDir() { - return ErrPerm("link", existingPath) - } - - newParentPath, newName := path.Split(newPath) - newParentPath = normalizePath(newParentPath) - - // Ensure parent exists - if err := fs.MkdirAll(ctx, newParentPath, 0o755); err != nil { - return err - } - - newParentIno, err := fs.resolvePathFollow(ctx, newParentPath, true) - if err != nil { - return err - } - - // Check if destination already exists - _, err = fs.lookupDentry(ctx, newParentIno, newName) - if err == nil { - return ErrExist("link", newPath) - } - - // Create dentry - if _, err := fs.db.ExecContext(ctx, insertDentry, newName, newParentIno, ino); err != nil { - return err - } - - // Increment nlink - if _, err := fs.db.ExecContext(ctx, incrementNlink, ino); err != nil { - return err - } - - return nil -} - -// Symlink creates a symbolic link. -func (fs *Filesystem) Symlink(ctx context.Context, target, linkPath string) error { - linkPath = normalizePath(linkPath) - - parentPath, name := path.Split(linkPath) - parentPath = normalizePath(parentPath) - - if err := validateName("symlink", name); err != nil { - return err - } - - // Ensure parent exists - if err := fs.MkdirAll(ctx, parentPath, 0o755); err != nil { - return err - } - - parentIno, err := fs.resolvePathFollow(ctx, parentPath, true) - if err != nil { - return err - } - - // Check if already exists - _, err = fs.lookupDentry(ctx, parentIno, name) - if err == nil { - return ErrExist("symlink", linkPath) - } - - // Create inode - now := time.Now() - nowSec := now.Unix() - nowNsec := int64(now.Nanosecond()) - var ino int64 - err = fs.db.QueryRowContext(ctx, insertInode, S_IFLNK|0o777, 0, 0, len(target), nowSec, nowSec, nowSec, 0, nowNsec, nowNsec, nowNsec).Scan(&ino) - if err != nil { - return err - } - - // Create symlink target - if _, err := fs.db.ExecContext(ctx, insertSymlink, ino, target); err != nil { - return err - } - - // Create dentry - if _, err := fs.db.ExecContext(ctx, insertDentry, name, parentIno, ino); err != nil { - return err - } - - // Increment nlink - if _, err := fs.db.ExecContext(ctx, incrementNlink, ino); err != nil { - return err - } - - return nil -} - -// Readlink returns the target of a symbolic link. -// Intermediate symlinks in the path are followed, but the final component is not. -func (fs *Filesystem) Readlink(ctx context.Context, p string) (string, error) { - p = normalizePath(p) - - ino, err := fs.resolvePathFollow(ctx, p, false) - if err != nil { - return "", err - } - - stats, err := fs.statInode(ctx, ino) - if err != nil { - return "", err - } - if !stats.IsSymlink() { - return "", ErrNotSymlink("readlink", p) - } - - return fs.readSymlinkTarget(ctx, ino) -} - -// Lstat returns file/directory metadata without following the final symlink. -// Intermediate symlinks in the path are still followed. -// If the path refers to a symlink, Lstat returns the symlink's own stats. -func (fs *Filesystem) Lstat(ctx context.Context, p string) (*Stats, error) { - p = normalizePath(p) - - ino, err := fs.resolvePathFollow(ctx, p, false) - if err != nil { - return nil, err - } - - return fs.statInode(ctx, ino) -} - -// Statfs returns aggregate filesystem statistics. -func (fs *Filesystem) Statfs(ctx context.Context) (*FilesystemStats, error) { - var stats FilesystemStats - - if err := fs.db.QueryRowContext(ctx, statfsInodeCount).Scan(&stats.Inodes); err != nil { - return nil, fmt.Errorf("statfs: failed to count inodes: %w", err) - } - - if err := fs.db.QueryRowContext(ctx, statfsBytesUsed).Scan(&stats.BytesUsed); err != nil { - return nil, fmt.Errorf("statfs: failed to sum bytes: %w", err) - } - - return &stats, nil -} - -// Chown changes file ownership. -// Pass -1 for uid or gid to leave that field unchanged. -func (fs *Filesystem) Chown(ctx context.Context, p string, uid, gid int64) error { - if uid == -1 && gid == -1 { - return nil - } - - p = normalizePath(p) - - ino, err := fs.resolvePathFollow(ctx, p, true) - if err != nil { - return err - } - - var setClauses []string - var args []any - - if uid != -1 { - setClauses = append(setClauses, "uid = ?") - args = append(args, uid) - } - if gid != -1 { - setClauses = append(setClauses, "gid = ?") - args = append(args, gid) - } - - now := time.Now() - setClauses = append(setClauses, "ctime = ?", "ctime_nsec = ?") - args = append(args, now.Unix(), int64(now.Nanosecond())) - - args = append(args, ino) - query := fmt.Sprintf("UPDATE fs_inode SET %s WHERE ino = ?", strings.Join(setClauses, ", ")) - - if _, err := fs.db.ExecContext(ctx, query, args...); err != nil { - return err - } - - return nil -} - -// Mknod creates a special file (FIFO, character device, block device, or socket). -// The mode parameter must include one of S_IFIFO, S_IFCHR, S_IFBLK, or S_IFSOCK. -// The rdev parameter specifies the device number (used for character and block devices). -func (fs *Filesystem) Mknod(ctx context.Context, p string, mode, rdev int64) error { - p = normalizePath(p) - if p == "/" { - return ErrRootOperation("mknod", p) - } - - parentPath, name := path.Split(p) - parentPath = normalizePath(parentPath) - - if err := validateName("mknod", name); err != nil { - return err - } - - // Validate that mode includes a special file type - ft := mode & S_IFMT - if ft != S_IFIFO && ft != S_IFCHR && ft != S_IFBLK && ft != S_IFSOCK { - return ErrInval("mknod", p, "mode must include S_IFIFO, S_IFCHR, S_IFBLK, or S_IFSOCK") - } - - parentIno, err := fs.resolvePathFollow(ctx, parentPath, true) - if err != nil { - return err - } - - // Check parent is a directory - parentStats, err := fs.statInode(ctx, parentIno) - if err != nil { - return err - } - if !parentStats.IsDir() { - return ErrNotDir("mknod", parentPath) - } - - // Check if entry already exists - _, err = fs.lookupDentry(ctx, parentIno, name) - if err == nil { - return ErrExist("mknod", p) - } - - // Create inode - now := time.Now() - nowSec := now.Unix() - nowNsec := int64(now.Nanosecond()) - - var ino int64 - err = fs.db.QueryRowContext(ctx, insertInode, mode, 0, 0, 0, nowSec, nowSec, nowSec, rdev, nowNsec, nowNsec, nowNsec).Scan(&ino) - if err != nil { - return err - } - - // Create dentry - if _, err := fs.db.ExecContext(ctx, insertDentry, name, parentIno, ino); err != nil { - return err - } - - // Increment nlink - if _, err := fs.db.ExecContext(ctx, incrementNlink, ino); err != nil { - return err - } - - return nil -} - -// Chmod changes file permissions. -func (fs *Filesystem) Chmod(ctx context.Context, p string, mode int64) error { - p = normalizePath(p) - - ino, err := fs.resolvePathFollow(ctx, p, true) - if err != nil { - return err - } - - stats, err := fs.statInode(ctx, ino) - if err != nil { - return err - } - - // Preserve file type, change permissions - newMode := (stats.Mode & S_IFMT) | (mode & 0o777) - now := time.Now() - - if _, err := fs.db.ExecContext(ctx, updateInodeMode, newMode, now.Unix(), int64(now.Nanosecond()), ino); err != nil { - return err - } - - return nil -} - -// Utimes updates file timestamps (seconds precision). -// For nanosecond precision or selective updates, use Utimens. -func (fs *Filesystem) Utimes(ctx context.Context, p string, atime, mtime int64) error { - return fs.Utimens(ctx, p, TimeSet(atime, 0), TimeSet(mtime, 0)) -} - -// UtimesNano updates file timestamps with nanosecond precision. -// For selective updates (omit or set to now), use Utimens. -func (fs *Filesystem) UtimesNano(ctx context.Context, p string, atimeSec, atimeNsec, mtimeSec, mtimeNsec int64) error { - return fs.Utimens(ctx, p, TimeSet(atimeSec, atimeNsec), TimeSet(mtimeSec, mtimeNsec)) -} - -// Utimens updates file timestamps with nanosecond precision and selective control. -// Each timestamp can be set to a specific value (TimeSet), the current time (TimeNow), -// or left unchanged (TimeOmit). -func (fs *Filesystem) Utimens(ctx context.Context, p string, atime, mtime TimeChange) error { - if atime.IsOmit() && mtime.IsOmit() { - return nil - } - - p = normalizePath(p) - - ino, err := fs.resolvePathFollow(ctx, p, true) - if err != nil { - return err - } - - now := time.Now() - - var setClauses []string - var args []any - - if !atime.IsOmit() { - var sec, nsec int64 - if atime.IsNow() { - sec, nsec = now.Unix(), int64(now.Nanosecond()) - } else { - sec, nsec = atime.sec, atime.nsec - } - setClauses = append(setClauses, "atime = ?", "atime_nsec = ?") - args = append(args, sec, nsec) - } - - if !mtime.IsOmit() { - var sec, nsec int64 - if mtime.IsNow() { - sec, nsec = now.Unix(), int64(now.Nanosecond()) - } else { - sec, nsec = mtime.sec, mtime.nsec - } - setClauses = append(setClauses, "mtime = ?", "mtime_nsec = ?") - args = append(args, sec, nsec) - } - - // Always update ctime - setClauses = append(setClauses, "ctime = ?", "ctime_nsec = ?") - args = append(args, now.Unix(), int64(now.Nanosecond())) - - args = append(args, ino) - query := fmt.Sprintf("UPDATE fs_inode SET %s WHERE ino = ?", strings.Join(setClauses, ", ")) - - if _, err := fs.db.ExecContext(ctx, query, args...); err != nil { - return err - } - - return nil -} - -// Open opens a file and returns a handle for read/write operations. -func (fs *Filesystem) Open(ctx context.Context, p string, flags int) (*File, error) { - p = normalizePath(p) - - ino, err := fs.resolvePathFollow(ctx, p, true) - if err != nil { - if IsNotExist(err) && (flags&O_CREATE) != 0 { - // Create the file - if err := fs.WriteFile(ctx, p, nil, 0o644); err != nil { - return nil, err - } - ino, err = fs.resolvePathFollow(ctx, p, true) - if err != nil { - return nil, err - } - } else { - return nil, err - } - } - - stats, err := fs.statInode(ctx, ino) - if err != nil { - return nil, err - } - if stats.IsDir() { - return nil, ErrIsDir("open", p) - } - - if (flags & O_TRUNC) != 0 { - // Truncate file - if _, err := fs.db.ExecContext(ctx, deleteChunksByIno, ino); err != nil { - return nil, err - } - now := time.Now() - if _, err := fs.db.ExecContext(ctx, updateInodeSize, 0, now.Unix(), int64(now.Nanosecond()), ino); err != nil { - return nil, err - } - } - - return &File{ - fs: fs, - ino: ino, - path: p, - flags: flags, - }, nil -} - -// Create creates a new file and returns its stats and a file handle. -func (fs *Filesystem) Create(ctx context.Context, p string, mode int64) (*Stats, *File, error) { - p = normalizePath(p) - - _, name := path.Split(p) - if err := validateName("create", name); err != nil { - return nil, nil, err - } - - // Create the file (or truncate if exists) - if err := fs.WriteFile(ctx, p, nil, mode); err != nil { - return nil, nil, err - } - - ino, err := fs.resolvePathFollow(ctx, p, true) - if err != nil { - return nil, nil, err - } - - stats, err := fs.statInode(ctx, ino) - if err != nil { - return nil, nil, err - } - - return stats, &File{ - fs: fs, - ino: ino, - path: p, - flags: O_RDWR, - }, nil -} - -// validateName checks that a filename component is valid. -func validateName(syscall, name string) error { - if len(name) > MaxNameLen { - return ErrNameTooLong(syscall, name) - } - return nil -} - -// Helper functions - -// normalizePath cleans and normalizes a path -func normalizePath(p string) string { - p = path.Clean(p) - if p == "." || p == "" { - return "/" - } - if !strings.HasPrefix(p, "/") { - p = "/" + p - } - return p -} - -// splitPath splits a path into components -func splitPath(p string) []string { - p = strings.Trim(p, "/") - if p == "" { - return nil - } - return strings.Split(p, "/") -} - -// lookupDentry looks up a directory entry -func (fs *Filesystem) lookupDentry(ctx context.Context, parentIno int64, name string) (int64, error) { - var ino int64 - err := fs.db.QueryRowContext(ctx, queryDentryByParentAndName, parentIno, name).Scan(&ino) - if err == sql.ErrNoRows { - return 0, ErrNoent("lookup", name) - } - return ino, err -} - -// lookupDentryWithMode looks up a directory entry and returns its inode number and mode -// in a single query (avoids two round-trips per path component). -func (fs *Filesystem) lookupDentryWithMode(ctx context.Context, parentIno int64, name string) (int64, int64, error) { - var ino, mode int64 - err := fs.db.QueryRowContext(ctx, queryDentryWithMode, parentIno, name).Scan(&ino, &mode) - if err == sql.ErrNoRows { - return 0, 0, ErrNoent("lookup", name) - } - return ino, mode, err -} - -// readSymlinkTarget reads the target of a symlink by inode number. -func (fs *Filesystem) readSymlinkTarget(ctx context.Context, ino int64) (string, error) { - var target string - if err := fs.db.QueryRowContext(ctx, querySymlinkTarget, ino).Scan(&target); err != nil { - return "", err - } - return target, nil -} - -// resolvePathFollow resolves a path to an inode number, following symlinks. -// If followLast is true, the final component is followed if it is a symlink. -// Returns ELOOP if more than MaxSymlinkDepth symlinks are encountered. -func (fs *Filesystem) resolvePathFollow(ctx context.Context, p string, followLast bool) (int64, error) { - p = normalizePath(p) - - if p == "/" { - return RootIno, nil - } - - symlinkCount := 0 - - for { - components := splitPath(p) - currentIno := int64(RootIno) - resolvedDir := "/" - - allResolved := true - for i, component := range components { - isLast := i == len(components)-1 - - childIno, childMode, err := fs.lookupDentryWithMode(ctx, currentIno, component) - if err != nil { - return 0, ErrNoent("resolve", p) - } - - isSymlink := (childMode & S_IFMT) == S_IFLNK - - if isSymlink && (!isLast || followLast) { - symlinkCount++ - if symlinkCount > MaxSymlinkDepth { - return 0, ErrLoop("resolve", p) - } - - target, err := fs.readSymlinkTarget(ctx, childIno) - if err != nil { - return 0, err - } - - // Build the remaining path after this component - remaining := strings.Join(components[i+1:], "/") - - if strings.HasPrefix(target, "/") { - // Absolute symlink target - if remaining != "" { - p = target + "/" + remaining - } else { - p = target - } - } else { - // Relative symlink target: resolve against parent directory - if remaining != "" { - p = resolvedDir + "/" + target + "/" + remaining - } else { - p = resolvedDir + "/" + target - } - } - p = normalizePath(p) - allResolved = false - break - } - - if !isLast { - isDir := (childMode & S_IFMT) == S_IFDIR - if !isDir && !isSymlink { - return 0, ErrNotDir("resolve", p) - } - } - - currentIno = childIno - if resolvedDir == "/" { - resolvedDir = "/" + component - } else { - resolvedDir = resolvedDir + "/" + component - } - } - - if allResolved { - return currentIno, nil - } - } -} - -// statInode retrieves stats for an inode -func (fs *Filesystem) statInode(ctx context.Context, ino int64) (*Stats, error) { - var s Stats - err := fs.db.QueryRowContext(ctx, queryInodeByIno, ino).Scan( - &s.Ino, &s.Mode, &s.Nlink, &s.UID, &s.GID, &s.Size, &s.Atime, &s.Mtime, &s.Ctime, &s.Rdev, - &s.AtimeNsec, &s.MtimeNsec, &s.CtimeNsec, - ) - if err == sql.ErrNoRows { - return nil, ErrNoent("stat", "") - } - return &s, err -} - -// writeChunks writes data in chunks to fs_data -func (fs *Filesystem) writeChunks(ctx context.Context, ino int64, data []byte) error { - chunkIndex := 0 - for len(data) > 0 { - chunkSize := fs.chunkSize - if len(data) < chunkSize { - chunkSize = len(data) - } - chunk := data[:chunkSize] - data = data[chunkSize:] - - if _, err := fs.db.ExecContext(ctx, insertChunk, ino, chunkIndex, chunk); err != nil { - return err - } - chunkIndex++ - } - return nil -} diff --git a/sdk/go/go.mod b/sdk/go/go.mod deleted file mode 100644 index 3d7f2ac2..00000000 --- a/sdk/go/go.mod +++ /dev/null @@ -1,21 +0,0 @@ -module github.com/tursodatabase/agentfs/sdk/go - -go 1.21 - -require modernc.org/sqlite v1.29.1 - -require ( - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/ncruces/go-strftime v0.1.9 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - golang.org/x/sys v0.16.0 // indirect - modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect - modernc.org/libc v1.41.0 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/strutil v1.2.0 // indirect - modernc.org/token v1.1.0 // indirect -) diff --git a/sdk/go/go.sum b/sdk/go/go.sum deleted file mode 100644 index 1d61df76..00000000 --- a/sdk/go/go.sum +++ /dev/null @@ -1,39 +0,0 @@ -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= -github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= -modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk= -modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/sqlite v1.29.1 h1:19GY2qvWB4VPw0HppFlZCPAbmxFU41r+qjKZQdQ1ryA= -modernc.org/sqlite v1.29.1/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/sdk/go/interfaces.go b/sdk/go/interfaces.go deleted file mode 100644 index 5264fba0..00000000 --- a/sdk/go/interfaces.go +++ /dev/null @@ -1,135 +0,0 @@ -package agentfs - -import ( - "context" - "encoding/json" -) - -// FileSystem defines the interface for filesystem operations. -// This interface is optional - the SDK returns concrete *Filesystem types, -// but users can program against this interface for testability and mocking. -type FileSystem interface { - // Stat returns file/directory metadata for the given path. - // If the path refers to a symlink, Stat follows the symlink. - Stat(ctx context.Context, path string) (*Stats, error) - - // Lstat returns file/directory metadata without following the final symlink. - Lstat(ctx context.Context, path string) (*Stats, error) - - // Readdir returns the names of entries in a directory. - Readdir(ctx context.Context, path string) ([]string, error) - - // ReaddirPlus returns directory entries with their stats. - ReaddirPlus(ctx context.Context, path string) ([]DirEntry, error) - - // Mkdir creates a directory. - Mkdir(ctx context.Context, path string, mode int64) error - - // MkdirAll creates a directory and all parent directories as needed. - MkdirAll(ctx context.Context, path string, mode int64) error - - // ReadFile reads the entire contents of a file. - ReadFile(ctx context.Context, path string) ([]byte, error) - - // WriteFile writes data to a file, creating it if it doesn't exist. - WriteFile(ctx context.Context, path string, data []byte, mode int64) error - - // Unlink removes a file. - Unlink(ctx context.Context, path string) error - - // Rmdir removes an empty directory. - Rmdir(ctx context.Context, path string) error - - // Rename moves or renames a file or directory. - Rename(ctx context.Context, oldPath, newPath string) error - - // Link creates a hard link. - Link(ctx context.Context, existingPath, newPath string) error - - // Symlink creates a symbolic link. - Symlink(ctx context.Context, target, linkPath string) error - - // Readlink returns the target of a symbolic link. - Readlink(ctx context.Context, path string) (string, error) - - // Chmod changes file permissions. - Chmod(ctx context.Context, path string, mode int64) error - - // Utimes updates file timestamps. - Utimes(ctx context.Context, path string, atime, mtime int64) error - - // Utimens updates file timestamps with selective control. - // Each timestamp can be TimeSet (specific value), TimeNow (current time), - // or TimeOmit (leave unchanged). - Utimens(ctx context.Context, path string, atime, mtime TimeChange) error - - // Open opens a file and returns a handle for read/write operations. - Open(ctx context.Context, path string, flags int) (*File, error) - - // Create creates a new file and returns its stats and a handle. - Create(ctx context.Context, path string, mode int64) (*Stats, *File, error) - - // ChunkSize returns the configured chunk size for file data. - ChunkSize() int -} - -// KVStoreInterface defines the interface for key-value store operations. -// This interface is optional - the SDK returns concrete *KVStore types, -// but users can program against this interface for testability and mocking. -type KVStoreInterface interface { - // Set stores a value (JSON-serialized) for the given key. - Set(ctx context.Context, key string, value any) error - - // Get retrieves a value and unmarshals it into dest. - Get(ctx context.Context, key string, dest any) error - - // GetRaw retrieves the raw JSON value for a key. - GetRaw(ctx context.Context, key string) (json.RawMessage, error) - - // Delete removes a key. - Delete(ctx context.Context, key string) error - - // Has checks if a key exists. - Has(ctx context.Context, key string) (bool, error) - - // Keys returns all keys, optionally filtered by prefix. - Keys(ctx context.Context, prefix string) ([]string, error) - - // List returns all key entries with metadata, optionally filtered by prefix. - List(ctx context.Context, prefix string) ([]KVEntry, error) - - // Clear removes all keys, optionally filtered by prefix. - Clear(ctx context.Context, prefix string) error -} - -// ToolCallsInterface defines the interface for tool call tracking operations. -// This interface is optional - the SDK returns concrete *ToolCalls types, -// but users can program against this interface for testability and mocking. -type ToolCallsInterface interface { - // Start begins tracking a tool call, returning a PendingCall for later completion. - Start(ctx context.Context, name string, parameters any) (*PendingCall, error) - - // Record inserts a complete tool call record directly. - // The errMsg parameter is the error message string (nil if successful). - Record(ctx context.Context, name string, parameters any, result any, errMsg *string, startedAt, completedAt int64) (*ToolCall, error) - - // Get retrieves a tool call by ID. - Get(ctx context.Context, id int64) (*ToolCall, error) - - // GetByName retrieves tool calls by name. - GetByName(ctx context.Context, name string, limit int) ([]ToolCall, error) - - // GetRecent retrieves recent tool calls since the given timestamp. - GetRecent(ctx context.Context, since int64, limit int) ([]ToolCall, error) - - // GetStats returns aggregated statistics for all tool calls. - GetStats(ctx context.Context) ([]ToolCallStats, error) -} - -// Compile-time interface satisfaction checks. -// These ensure the concrete types implement their respective interfaces. -var ( - _ FileSystem = (*Filesystem)(nil) - _ KVStoreInterface = (*KVStore)(nil) - _ ToolCallsInterface = (*ToolCalls)(nil) -) diff --git a/sdk/go/internal/cache/cache.go b/sdk/go/internal/cache/cache.go deleted file mode 100644 index 084b8ded..00000000 --- a/sdk/go/internal/cache/cache.go +++ /dev/null @@ -1,42 +0,0 @@ -// Package cache provides caching implementations for AgentFS. -package cache - -// PathCache caches path-to-inode resolution results. -type PathCache interface { - // Get returns the inode for a path, or (0, false) if not cached. - Get(path string) (ino int64, ok bool) - - // Set caches a path-to-inode mapping. - Set(path string, ino int64) - - // Delete removes a single path from the cache. - Delete(path string) - - // DeletePrefix removes all paths with the given prefix. - // Used when a directory is deleted or renamed. - DeletePrefix(prefix string) - - // Clear removes all entries. - Clear() - - // Stats returns cache statistics. - Stats() Stats -} - -// Stats contains cache performance statistics. -type Stats struct { - Hits int64 // Number of cache hits - Misses int64 // Number of cache misses - Entries int // Current number of entries - MaxEntries int // Maximum capacity -} - -// HitRate returns the cache hit rate as a percentage (0-100). -// Returns 0 if no lookups have been performed. -func (s Stats) HitRate() float64 { - total := s.Hits + s.Misses - if total == 0 { - return 0 - } - return float64(s.Hits) / float64(total) * 100 -} diff --git a/sdk/go/internal/cache/lru.go b/sdk/go/internal/cache/lru.go deleted file mode 100644 index b711cc64..00000000 --- a/sdk/go/internal/cache/lru.go +++ /dev/null @@ -1,121 +0,0 @@ -package cache - -import ( - "strings" - "sync" - "sync/atomic" - "time" - - lru "github.com/hashicorp/golang-lru/v2" -) - -// lruCache implements PathCache using an LRU eviction policy. -type lruCache struct { - cache *lru.Cache[string, cacheEntry] - ttl time.Duration - mu sync.RWMutex - hits atomic.Int64 - misses atomic.Int64 - maxEntries int -} - -// cacheEntry stores the cached inode and optional expiration time. -type cacheEntry struct { - ino int64 - expiresAt time.Time // zero if no TTL -} - -// NewLRU creates a new LRU-based path cache. -// -// Parameters: -// - maxEntries: maximum number of entries to cache -// - ttl: time-to-live for entries (0 = no expiration) -func NewLRU(maxEntries int, ttl time.Duration) (PathCache, error) { - cache, err := lru.New[string, cacheEntry](maxEntries) - if err != nil { - return nil, err - } - return &lruCache{ - cache: cache, - ttl: ttl, - maxEntries: maxEntries, - }, nil -} - -// Get returns the inode for a path, or (0, false) if not cached or expired. -func (c *lruCache) Get(path string) (int64, bool) { - c.mu.RLock() - entry, ok := c.cache.Get(path) - c.mu.RUnlock() - - if !ok { - c.misses.Add(1) - return 0, false - } - - // Check TTL - if !entry.expiresAt.IsZero() && time.Now().After(entry.expiresAt) { - c.mu.Lock() - c.cache.Remove(path) - c.mu.Unlock() - c.misses.Add(1) - return 0, false - } - - c.hits.Add(1) - return entry.ino, true -} - -// Set caches a path-to-inode mapping. -func (c *lruCache) Set(path string, ino int64) { - entry := cacheEntry{ino: ino} - if c.ttl > 0 { - entry.expiresAt = time.Now().Add(c.ttl) - } - - c.mu.Lock() - c.cache.Add(path, entry) - c.mu.Unlock() -} - -// Delete removes a single path from the cache. -func (c *lruCache) Delete(path string) { - c.mu.Lock() - c.cache.Remove(path) - c.mu.Unlock() -} - -// DeletePrefix removes all paths with the given prefix. -func (c *lruCache) DeletePrefix(prefix string) { - c.mu.Lock() - defer c.mu.Unlock() - - // Get all keys and remove those with matching prefix - keys := c.cache.Keys() - for _, key := range keys { - if strings.HasPrefix(key, prefix) { - c.cache.Remove(key) - } - } -} - -// Clear removes all entries from the cache. -func (c *lruCache) Clear() { - c.mu.Lock() - c.cache.Purge() - c.mu.Unlock() -} - -// Stats returns cache statistics. -func (c *lruCache) Stats() Stats { - c.mu.RLock() - entries := c.cache.Len() - c.mu.RUnlock() - - return Stats{ - Hits: c.hits.Load(), - Misses: c.misses.Load(), - Entries: entries, - MaxEntries: c.maxEntries, - } -} diff --git a/sdk/go/internal/cache/lru_test.go b/sdk/go/internal/cache/lru_test.go deleted file mode 100644 index fa7c83a9..00000000 --- a/sdk/go/internal/cache/lru_test.go +++ /dev/null @@ -1,247 +0,0 @@ -package cache - -import ( - "testing" - "time" -) - -func TestLRUCache_BasicOperations(t *testing.T) { - cache, err := NewLRU(100, 0) - if err != nil { - t.Fatalf("NewLRU failed: %v", err) - } - - // Test Set and Get - cache.Set("/foo/bar", 42) - - ino, ok := cache.Get("/foo/bar") - if !ok { - t.Error("Expected cache hit for /foo/bar") - } - if ino != 42 { - t.Errorf("Expected ino 42, got %d", ino) - } - - // Test cache miss - _, ok = cache.Get("/nonexistent") - if ok { - t.Error("Expected cache miss for /nonexistent") - } -} - -func TestLRUCache_Delete(t *testing.T) { - cache, _ := NewLRU(100, 0) - - cache.Set("/foo", 1) - cache.Set("/bar", 2) - - cache.Delete("/foo") - - _, ok := cache.Get("/foo") - if ok { - t.Error("Expected cache miss after delete") - } - - ino, ok := cache.Get("/bar") - if !ok || ino != 2 { - t.Error("Other entries should not be affected") - } -} - -func TestLRUCache_DeletePrefix(t *testing.T) { - cache, _ := NewLRU(100, 0) - - cache.Set("/dir", 1) - cache.Set("/dir/a", 2) - cache.Set("/dir/b", 3) - cache.Set("/dir/sub/c", 4) - cache.Set("/other", 5) - - cache.DeletePrefix("/dir/") - - // /dir itself should still exist - if _, ok := cache.Get("/dir"); !ok { - t.Error("/dir should still exist") - } - - // Children should be deleted - if _, ok := cache.Get("/dir/a"); ok { - t.Error("/dir/a should be deleted") - } - if _, ok := cache.Get("/dir/b"); ok { - t.Error("/dir/b should be deleted") - } - if _, ok := cache.Get("/dir/sub/c"); ok { - t.Error("/dir/sub/c should be deleted") - } - - // Other paths should remain - if _, ok := cache.Get("/other"); !ok { - t.Error("/other should still exist") - } -} - -func TestLRUCache_Clear(t *testing.T) { - cache, _ := NewLRU(100, 0) - - cache.Set("/a", 1) - cache.Set("/b", 2) - cache.Set("/c", 3) - - cache.Clear() - - if _, ok := cache.Get("/a"); ok { - t.Error("Cache should be empty after clear") - } - if _, ok := cache.Get("/b"); ok { - t.Error("Cache should be empty after clear") - } - - stats := cache.Stats() - if stats.Entries != 0 { - t.Errorf("Expected 0 entries, got %d", stats.Entries) - } -} - -func TestLRUCache_Stats(t *testing.T) { - cache, _ := NewLRU(100, 0) - - // Initial stats - stats := cache.Stats() - if stats.Hits != 0 || stats.Misses != 0 { - t.Error("Initial stats should be zero") - } - if stats.MaxEntries != 100 { - t.Errorf("Expected MaxEntries 100, got %d", stats.MaxEntries) - } - - // Generate some hits and misses - cache.Set("/exists", 1) - cache.Get("/exists") // hit - cache.Get("/exists") // hit - cache.Get("/missing") // miss - - stats = cache.Stats() - if stats.Hits != 2 { - t.Errorf("Expected 2 hits, got %d", stats.Hits) - } - if stats.Misses != 1 { - t.Errorf("Expected 1 miss, got %d", stats.Misses) - } - if stats.Entries != 1 { - t.Errorf("Expected 1 entry, got %d", stats.Entries) - } -} - -func TestLRUCache_HitRate(t *testing.T) { - stats := Stats{Hits: 75, Misses: 25} - rate := stats.HitRate() - if rate != 75.0 { - t.Errorf("Expected 75.0%% hit rate, got %f", rate) - } - - // Zero case - stats = Stats{Hits: 0, Misses: 0} - rate = stats.HitRate() - if rate != 0 { - t.Errorf("Expected 0%% hit rate for empty stats, got %f", rate) - } -} - -func TestLRUCache_Eviction(t *testing.T) { - cache, _ := NewLRU(3, 0) - - cache.Set("/a", 1) - cache.Set("/b", 2) - cache.Set("/c", 3) - - // Access /a to make it recently used - cache.Get("/a") - - // Add /d, should evict /b (least recently used) - cache.Set("/d", 4) - - if _, ok := cache.Get("/a"); !ok { - t.Error("/a should still exist (recently used)") - } - if _, ok := cache.Get("/b"); ok { - t.Error("/b should be evicted (LRU)") - } - if _, ok := cache.Get("/c"); !ok { - t.Error("/c should still exist") - } - if _, ok := cache.Get("/d"); !ok { - t.Error("/d should exist") - } -} - -func TestLRUCache_TTL(t *testing.T) { - cache, _ := NewLRU(100, 50*time.Millisecond) - - cache.Set("/expires", 1) - - // Should exist immediately - if _, ok := cache.Get("/expires"); !ok { - t.Error("Entry should exist immediately") - } - - // Wait for TTL to expire - time.Sleep(60 * time.Millisecond) - - // Should be expired now - if _, ok := cache.Get("/expires"); ok { - t.Error("Entry should have expired") - } -} - -func TestLRUCache_NoTTL(t *testing.T) { - cache, _ := NewLRU(100, 0) // 0 = no TTL - - cache.Set("/permanent", 1) - - // Wait a bit - time.Sleep(10 * time.Millisecond) - - // Should still exist - if _, ok := cache.Get("/permanent"); !ok { - t.Error("Entry with no TTL should not expire") - } -} - -func TestLRUCache_Concurrent(t *testing.T) { - cache, _ := NewLRU(1000, 0) - - // Run concurrent operations - done := make(chan bool) - - // Writer goroutine - go func() { - for i := 0; i < 100; i++ { - cache.Set("/path", int64(i)) - } - done <- true - }() - - // Reader goroutine - go func() { - for i := 0; i < 100; i++ { - cache.Get("/path") - } - done <- true - }() - - // Deleter goroutine - go func() { - for i := 0; i < 100; i++ { - cache.Delete("/other") - } - done <- true - }() - - // Wait for all goroutines - <-done - <-done - <-done - - // Should not panic or deadlock -} diff --git a/sdk/go/iofs.go b/sdk/go/iofs.go deleted file mode 100644 index 66b1840c..00000000 --- a/sdk/go/iofs.go +++ /dev/null @@ -1,476 +0,0 @@ -package agentfs - -import ( - "context" - "io" - "io/fs" - "path" - "sort" - "time" -) - -// IOFS wraps a Filesystem to implement Go's io/fs interfaces. -// It implements: fs.FS, fs.StatFS, fs.ReadFileFS, fs.ReadDirFS, fs.GlobFS, fs.SubFS -// -// Since io/fs interfaces don't support context.Context, use WithContext() to -// set a context for operations, or the default context.Background() is used. -type IOFS struct { - fs *Filesystem - ctx context.Context - root string // Subdirectory root (for SubFS), empty means "/" -} - -// Compile-time interface compliance checks -var ( - _ fs.FS = (*IOFS)(nil) - _ fs.StatFS = (*IOFS)(nil) - _ fs.ReadFileFS = (*IOFS)(nil) - _ fs.ReadDirFS = (*IOFS)(nil) - _ fs.SubFS = (*IOFS)(nil) - // Note: We intentionally do NOT implement fs.GlobFS. - // The standard library's fs.Glob will use our ReadDir implementation. - // Implementing GlobFS by delegating to fs.Glob causes infinite recursion. -) - -// NewIOFS creates an io/fs compatible wrapper around a Filesystem. -// -// Example usage: -// -// afs, _ := agentfs.Open(ctx, agentfs.AgentFSOptions{ID: "my-agent"}) -// iofs := agentfs.NewIOFS(afs.FS) -// -// // Use with standard library functions -// fs.WalkDir(iofs, ".", walkFunc) -// template.ParseFS(iofs, "templates/*.html") -// http.FileServer(http.FS(iofs)) -func NewIOFS(filesystem *Filesystem) *IOFS { - return &IOFS{ - fs: filesystem, - ctx: context.Background(), - root: "", - } -} - -// WithContext returns a copy of the IOFS with the given context. -// This context will be used for all filesystem operations. -// -// Example: -// -// ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) -// defer cancel() -// iofsWithTimeout := iofs.WithContext(ctx) -func (f *IOFS) WithContext(ctx context.Context) *IOFS { - return &IOFS{ - fs: f.fs, - ctx: ctx, - root: f.root, - } -} - -// toAbsPath converts an io/fs relative path to an AgentFS absolute path. -// io/fs uses paths like "foo/bar.txt" while AgentFS uses "/foo/bar.txt". -func (f *IOFS) toAbsPath(name string) string { - if name == "." || name == "" { - if f.root == "" { - return "/" - } - return f.root - } - if f.root == "" { - return "/" + name - } - return f.root + "/" + name -} - -// Open implements fs.FS. -// Opens the named file for reading. -func (f *IOFS) Open(name string) (fs.File, error) { - if !fs.ValidPath(name) { - return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid} - } - - absPath := f.toAbsPath(name) - stats, err := f.fs.Stat(f.ctx, absPath) - if err != nil { - if IsNotExist(err) { - return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} - } - return nil, &fs.PathError{Op: "open", Path: name, Err: err} - } - - // Handle directories - if stats.IsDir() { - return &iofsDir{ - iofs: f, - path: absPath, - name: baseName(name), - stats: stats, - }, nil - } - - // Handle regular files - file, err := f.fs.Open(f.ctx, absPath, O_RDONLY) - if err != nil { - return nil, &fs.PathError{Op: "open", Path: name, Err: err} - } - - return &iofsFile{ - file: file, - ctx: f.ctx, - name: baseName(name), - stats: stats, - offset: 0, - }, nil -} - -// Stat implements fs.StatFS. -// Returns a FileInfo describing the named file. -func (f *IOFS) Stat(name string) (fs.FileInfo, error) { - if !fs.ValidPath(name) { - return nil, &fs.PathError{Op: "stat", Path: name, Err: fs.ErrInvalid} - } - - stats, err := f.fs.Stat(f.ctx, f.toAbsPath(name)) - if err != nil { - if IsNotExist(err) { - return nil, &fs.PathError{Op: "stat", Path: name, Err: fs.ErrNotExist} - } - return nil, &fs.PathError{Op: "stat", Path: name, Err: err} - } - - return &iofsFileInfo{stats: stats, name: baseName(name)}, nil -} - -// ReadFile implements fs.ReadFileFS. -// Reads and returns the entire contents of the named file. -func (f *IOFS) ReadFile(name string) ([]byte, error) { - if !fs.ValidPath(name) { - return nil, &fs.PathError{Op: "readfile", Path: name, Err: fs.ErrInvalid} - } - - data, err := f.fs.ReadFile(f.ctx, f.toAbsPath(name)) - if err != nil { - if IsNotExist(err) { - return nil, &fs.PathError{Op: "readfile", Path: name, Err: fs.ErrNotExist} - } - return nil, &fs.PathError{Op: "readfile", Path: name, Err: err} - } - - return data, nil -} - -// ReadDir implements fs.ReadDirFS. -// Reads the named directory and returns a list of directory entries sorted by name. -func (f *IOFS) ReadDir(name string) ([]fs.DirEntry, error) { - if !fs.ValidPath(name) { - return nil, &fs.PathError{Op: "readdir", Path: name, Err: fs.ErrInvalid} - } - - entries, err := f.fs.ReaddirPlus(f.ctx, f.toAbsPath(name)) - if err != nil { - if IsNotExist(err) { - return nil, &fs.PathError{Op: "readdir", Path: name, Err: fs.ErrNotExist} - } - var fsErr *FSError - if ok := errorAs(err, &fsErr); ok && fsErr.Code == ENOTDIR { - return nil, &fs.PathError{Op: "readdir", Path: name, Err: fs.ErrInvalid} - } - return nil, &fs.PathError{Op: "readdir", Path: name, Err: err} - } - - result := make([]fs.DirEntry, len(entries)) - for i := range entries { - result[i] = &iofsDirEntry{entry: &entries[i]} - } - - // Sort by name (should already be sorted, but ensure it) - sort.Slice(result, func(i, j int) bool { - return result[i].Name() < result[j].Name() - }) - - return result, nil -} - -// Sub implements fs.SubFS. -// Returns an FS corresponding to the subtree rooted at dir. -func (f *IOFS) Sub(dir string) (fs.FS, error) { - if !fs.ValidPath(dir) { - return nil, &fs.PathError{Op: "sub", Path: dir, Err: fs.ErrInvalid} - } - - if dir == "." { - return f, nil - } - - absDir := f.toAbsPath(dir) - stats, err := f.fs.Stat(f.ctx, absDir) - if err != nil { - if IsNotExist(err) { - return nil, &fs.PathError{Op: "sub", Path: dir, Err: fs.ErrNotExist} - } - return nil, &fs.PathError{Op: "sub", Path: dir, Err: err} - } - - if !stats.IsDir() { - return nil, &fs.PathError{Op: "sub", Path: dir, Err: fs.ErrInvalid} - } - - return &IOFS{ - fs: f.fs, - ctx: f.ctx, - root: absDir, - }, nil -} - -// baseName returns the base name of a path, handling "." specially -func baseName(name string) string { - if name == "." || name == "" { - return "." - } - return path.Base(name) -} - -// errorAs is a helper to avoid import cycle with errors package -func errorAs(err error, target interface{}) bool { - if err == nil { - return false - } - if fsErr, ok := target.(**FSError); ok { - if e, ok := err.(*FSError); ok { - *fsErr = e - return true - } - } - return false -} - -// ============================================================================ -// iofsFile - implements fs.File for regular files -// ============================================================================ - -// iofsFile wraps File to implement fs.File with stateful reading. -type iofsFile struct { - file *File - ctx context.Context - name string - stats *Stats - offset int64 -} - -// Stat implements fs.File. -func (f *iofsFile) Stat() (fs.FileInfo, error) { - // Refresh stats - stats, err := f.file.Stat(f.ctx) - if err != nil { - return nil, err - } - return &iofsFileInfo{stats: stats, name: f.name}, nil -} - -// Read implements fs.File. -// Reads up to len(b) bytes into b, advancing the file offset. -func (f *iofsFile) Read(b []byte) (int, error) { - if len(b) == 0 { - return 0, nil - } - - n, err := f.file.Pread(f.ctx, b, f.offset) - f.offset += int64(n) - - // Convert zero-read to EOF - if n == 0 && err == nil { - return 0, io.EOF - } - - return n, err -} - -// Close implements fs.File. -func (f *iofsFile) Close() error { - return f.file.Close() -} - -// ============================================================================ -// iofsDir - implements fs.File and fs.ReadDirFile for directories -// ============================================================================ - -// iofsDir implements fs.File and fs.ReadDirFile for directories. -type iofsDir struct { - iofs *IOFS - path string - name string - stats *Stats - entries []fs.DirEntry // Lazy-loaded - offset int // Current position for ReadDir iteration -} - -// Compile-time check that iofsDir implements fs.ReadDirFile -var _ fs.ReadDirFile = (*iofsDir)(nil) - -// Stat implements fs.File. -func (d *iofsDir) Stat() (fs.FileInfo, error) { - return &iofsFileInfo{stats: d.stats, name: d.name}, nil -} - -// Read implements fs.File. -// Reading from a directory is not permitted. -func (d *iofsDir) Read(b []byte) (int, error) { - return 0, &fs.PathError{Op: "read", Path: d.name, Err: fs.ErrInvalid} -} - -// Close implements fs.File. -func (d *iofsDir) Close() error { - d.entries = nil - d.offset = 0 - return nil -} - -// ReadDir implements fs.ReadDirFile. -// Reads the contents of the directory and returns up to n DirEntry values. -// If n <= 0, ReadDir returns all entries. -func (d *iofsDir) ReadDir(n int) ([]fs.DirEntry, error) { - // Lazy-load entries on first call - if d.entries == nil { - entries, err := d.iofs.fs.ReaddirPlus(d.iofs.ctx, d.path) - if err != nil { - return nil, err - } - - d.entries = make([]fs.DirEntry, len(entries)) - for i := range entries { - d.entries[i] = &iofsDirEntry{entry: &entries[i]} - } - - // Sort by name - sort.Slice(d.entries, func(i, j int) bool { - return d.entries[i].Name() < d.entries[j].Name() - }) - } - - // Return all remaining entries - if n <= 0 { - if d.offset >= len(d.entries) { - return nil, nil - } - entries := d.entries[d.offset:] - d.offset = len(d.entries) - return entries, nil - } - - // Return up to n entries - if d.offset >= len(d.entries) { - return nil, io.EOF - } - - end := d.offset + n - if end > len(d.entries) { - end = len(d.entries) - } - - entries := d.entries[d.offset:end] - d.offset = end - - return entries, nil -} - -// ============================================================================ -// iofsFileInfo - implements fs.FileInfo -// ============================================================================ - -// iofsFileInfo adapts *Stats to implement fs.FileInfo. -type iofsFileInfo struct { - stats *Stats - name string -} - -// Compile-time check -var _ fs.FileInfo = (*iofsFileInfo)(nil) - -// Name returns the base name of the file. -func (fi *iofsFileInfo) Name() string { - return fi.name -} - -// Size returns the file size in bytes. -func (fi *iofsFileInfo) Size() int64 { - return fi.stats.Size -} - -// Mode returns the file mode bits. -func (fi *iofsFileInfo) Mode() fs.FileMode { - return statsToFileMode(fi.stats) -} - -// ModTime returns the modification time. -func (fi *iofsFileInfo) ModTime() time.Time { - return time.Unix(fi.stats.Mtime, 0) -} - -// IsDir reports whether the file is a directory. -func (fi *iofsFileInfo) IsDir() bool { - return fi.stats.IsDir() -} - -// Sys returns the underlying *Stats. -func (fi *iofsFileInfo) Sys() any { - return fi.stats -} - -// ============================================================================ -// iofsDirEntry - implements fs.DirEntry -// ============================================================================ - -// iofsDirEntry adapts *DirEntry to implement fs.DirEntry. -type iofsDirEntry struct { - entry *DirEntry -} - -// Compile-time check -var _ fs.DirEntry = (*iofsDirEntry)(nil) - -// Name returns the name of the file or directory. -func (de *iofsDirEntry) Name() string { - return de.entry.Name -} - -// IsDir reports whether the entry describes a directory. -func (de *iofsDirEntry) IsDir() bool { - return de.entry.Stats.IsDir() -} - -// Type returns the type bits for the entry (ModeDir, ModeSymlink, etc.). -func (de *iofsDirEntry) Type() fs.FileMode { - return statsToFileMode(de.entry.Stats).Type() -} - -// Info returns the FileInfo for the file or directory. -func (de *iofsDirEntry) Info() (fs.FileInfo, error) { - return &iofsFileInfo{stats: de.entry.Stats, name: de.entry.Name}, nil -} - -// ============================================================================ -// Helper functions -// ============================================================================ - -// statsToFileMode converts Stats.Mode to fs.FileMode. -func statsToFileMode(s *Stats) fs.FileMode { - mode := fs.FileMode(s.Mode & 0o777) // Permission bits - - // File type - switch s.Mode & S_IFMT { - case S_IFDIR: - mode |= fs.ModeDir - case S_IFLNK: - mode |= fs.ModeSymlink - case S_IFIFO: - mode |= fs.ModeNamedPipe - case S_IFSOCK: - mode |= fs.ModeSocket - case S_IFCHR: - mode |= fs.ModeDevice | fs.ModeCharDevice - case S_IFBLK: - mode |= fs.ModeDevice - } - - return mode -} diff --git a/sdk/go/iofs_test.go b/sdk/go/iofs_test.go deleted file mode 100644 index 898d2003..00000000 --- a/sdk/go/iofs_test.go +++ /dev/null @@ -1,568 +0,0 @@ -package agentfs - -import ( - "context" - "io" - "io/fs" - "testing" - "testing/fstest" -) - -func TestIOFS_FSInterface(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - // Create test files - afs.FS.WriteFile(ctx, "/hello.txt", []byte("Hello, World!"), 0o644) - afs.FS.WriteFile(ctx, "/subdir/nested.txt", []byte("Nested content"), 0o644) - afs.FS.MkdirAll(ctx, "/empty", 0o755) - - iofs := NewIOFS(afs.FS) - - t.Run("Open file", func(t *testing.T) { - f, err := iofs.Open("hello.txt") - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer f.Close() - - data, err := io.ReadAll(f) - if err != nil { - t.Fatalf("ReadAll failed: %v", err) - } - if string(data) != "Hello, World!" { - t.Errorf("Content = %q, want %q", data, "Hello, World!") - } - }) - - t.Run("Open directory", func(t *testing.T) { - f, err := iofs.Open(".") - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer f.Close() - - info, err := f.Stat() - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !info.IsDir() { - t.Error("Root should be a directory") - } - }) - - t.Run("Open non-existent", func(t *testing.T) { - _, err := iofs.Open("nonexistent.txt") - if err == nil { - t.Error("Expected error for non-existent file") - } - if !isPathError(err, fs.ErrNotExist) { - t.Errorf("Expected ErrNotExist, got %v", err) - } - }) - - t.Run("Open invalid path", func(t *testing.T) { - _, err := iofs.Open("/absolute/path") - if err == nil { - t.Error("Expected error for invalid path") - } - if !isPathError(err, fs.ErrInvalid) { - t.Errorf("Expected ErrInvalid, got %v", err) - } - }) - - t.Run("Open nested file", func(t *testing.T) { - f, err := iofs.Open("subdir/nested.txt") - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer f.Close() - - data, err := io.ReadAll(f) - if err != nil { - t.Fatalf("ReadAll failed: %v", err) - } - if string(data) != "Nested content" { - t.Errorf("Content = %q, want %q", data, "Nested content") - } - }) -} - -func TestIOFS_StatFS(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - afs.FS.WriteFile(ctx, "/test.txt", []byte("test content"), 0o644) - afs.FS.MkdirAll(ctx, "/mydir", 0o755) - - iofs := NewIOFS(afs.FS) - - t.Run("Stat file", func(t *testing.T) { - info, err := iofs.Stat("test.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if info.Name() != "test.txt" { - t.Errorf("Name = %q, want %q", info.Name(), "test.txt") - } - if info.Size() != 12 { - t.Errorf("Size = %d, want 12", info.Size()) - } - if info.IsDir() { - t.Error("Should not be a directory") - } - }) - - t.Run("Stat directory", func(t *testing.T) { - info, err := iofs.Stat("mydir") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !info.IsDir() { - t.Error("Should be a directory") - } - if info.Mode()&fs.ModeDir == 0 { - t.Error("Mode should have ModeDir set") - } - }) - - t.Run("Stat root", func(t *testing.T) { - info, err := iofs.Stat(".") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !info.IsDir() { - t.Error("Root should be a directory") - } - }) - - t.Run("Stat non-existent", func(t *testing.T) { - _, err := iofs.Stat("nonexistent") - if err == nil { - t.Error("Expected error") - } - }) -} - -func TestIOFS_ReadFileFS(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - content := []byte("File content for ReadFile test") - afs.FS.WriteFile(ctx, "/readable.txt", content, 0o644) - - iofs := NewIOFS(afs.FS) - - t.Run("ReadFile", func(t *testing.T) { - data, err := iofs.ReadFile("readable.txt") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - if string(data) != string(content) { - t.Errorf("Content = %q, want %q", data, content) - } - }) - - t.Run("ReadFile non-existent", func(t *testing.T) { - _, err := iofs.ReadFile("nonexistent.txt") - if err == nil { - t.Error("Expected error") - } - }) -} - -func TestIOFS_ReadDirFS(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - afs.FS.WriteFile(ctx, "/a.txt", []byte("a"), 0o644) - afs.FS.WriteFile(ctx, "/b.txt", []byte("b"), 0o644) - afs.FS.MkdirAll(ctx, "/subdir", 0o755) - afs.FS.WriteFile(ctx, "/subdir/c.txt", []byte("c"), 0o644) - - iofs := NewIOFS(afs.FS) - - t.Run("ReadDir root", func(t *testing.T) { - entries, err := iofs.ReadDir(".") - if err != nil { - t.Fatalf("ReadDir failed: %v", err) - } - - // Should have a.txt, b.txt, subdir - if len(entries) != 3 { - t.Errorf("Got %d entries, want 3", len(entries)) - } - - // Verify sorted order - names := make([]string, len(entries)) - for i, e := range entries { - names[i] = e.Name() - } - if names[0] != "a.txt" || names[1] != "b.txt" || names[2] != "subdir" { - t.Errorf("Entries not sorted: %v", names) - } - - // Check IsDir - for _, e := range entries { - if e.Name() == "subdir" { - if !e.IsDir() { - t.Error("subdir should be a directory") - } - } else { - if e.IsDir() { - t.Errorf("%s should not be a directory", e.Name()) - } - } - } - }) - - t.Run("ReadDir subdirectory", func(t *testing.T) { - entries, err := iofs.ReadDir("subdir") - if err != nil { - t.Fatalf("ReadDir failed: %v", err) - } - - if len(entries) != 1 { - t.Errorf("Got %d entries, want 1", len(entries)) - } - if entries[0].Name() != "c.txt" { - t.Errorf("Entry name = %q, want %q", entries[0].Name(), "c.txt") - } - }) - - t.Run("DirEntry Info", func(t *testing.T) { - entries, _ := iofs.ReadDir(".") - for _, e := range entries { - info, err := e.Info() - if err != nil { - t.Errorf("Info() failed for %s: %v", e.Name(), err) - } - if info.Name() != e.Name() { - t.Errorf("Info().Name() = %q, want %q", info.Name(), e.Name()) - } - } - }) -} - -func TestIOFS_ReadDirFile(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - afs.FS.WriteFile(ctx, "/1.txt", []byte("1"), 0o644) - afs.FS.WriteFile(ctx, "/2.txt", []byte("2"), 0o644) - afs.FS.WriteFile(ctx, "/3.txt", []byte("3"), 0o644) - - iofs := NewIOFS(afs.FS) - - t.Run("ReadDir incremental", func(t *testing.T) { - f, err := iofs.Open(".") - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer f.Close() - - dir, ok := f.(fs.ReadDirFile) - if !ok { - t.Fatal("Directory file should implement ReadDirFile") - } - - // Read 2 entries - entries, err := dir.ReadDir(2) - if err != nil { - t.Fatalf("ReadDir(2) failed: %v", err) - } - if len(entries) != 2 { - t.Errorf("Got %d entries, want 2", len(entries)) - } - - // Read 2 more (should get 1 remaining) - entries, err = dir.ReadDir(2) - if err != nil { - t.Fatalf("ReadDir(2) failed: %v", err) - } - if len(entries) != 1 { - t.Errorf("Got %d entries, want 1", len(entries)) - } - - // Read more (should get EOF) - _, err = dir.ReadDir(1) - if err != io.EOF { - t.Errorf("Expected EOF, got %v", err) - } - }) - - t.Run("ReadDir all at once", func(t *testing.T) { - f, _ := iofs.Open(".") - defer f.Close() - - dir := f.(fs.ReadDirFile) - entries, err := dir.ReadDir(-1) - if err != nil { - t.Fatalf("ReadDir(-1) failed: %v", err) - } - if len(entries) != 3 { - t.Errorf("Got %d entries, want 3", len(entries)) - } - }) -} - -func TestIOFS_Glob(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - afs.FS.WriteFile(ctx, "/a.txt", []byte("a"), 0o644) - afs.FS.WriteFile(ctx, "/b.txt", []byte("b"), 0o644) - afs.FS.WriteFile(ctx, "/c.md", []byte("c"), 0o644) - afs.FS.WriteFile(ctx, "/subdir/d.txt", []byte("d"), 0o644) - - iofs := NewIOFS(afs.FS) - - t.Run("Glob *.txt", func(t *testing.T) { - // Use fs.Glob from standard library (works with our ReadDir implementation) - matches, err := fs.Glob(iofs, "*.txt") - if err != nil { - t.Fatalf("Glob failed: %v", err) - } - if len(matches) != 2 { - t.Errorf("Got %d matches, want 2: %v", len(matches), matches) - } - }) - - t.Run("Glob */*.txt", func(t *testing.T) { - matches, err := fs.Glob(iofs, "*/*.txt") - if err != nil { - t.Fatalf("Glob failed: %v", err) - } - if len(matches) != 1 { - t.Errorf("Got %d matches, want 1: %v", len(matches), matches) - } - }) - - t.Run("Glob no matches", func(t *testing.T) { - matches, err := fs.Glob(iofs, "*.xyz") - if err != nil { - t.Fatalf("Glob failed: %v", err) - } - if len(matches) != 0 { - t.Errorf("Got %d matches, want 0", len(matches)) - } - }) -} - -func TestIOFS_SubFS(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - afs.FS.WriteFile(ctx, "/root.txt", []byte("root"), 0o644) - afs.FS.WriteFile(ctx, "/sub/file.txt", []byte("sub file"), 0o644) - afs.FS.WriteFile(ctx, "/sub/deep/nested.txt", []byte("nested"), 0o644) - - iofs := NewIOFS(afs.FS) - - t.Run("Sub directory", func(t *testing.T) { - subFS, err := iofs.Sub("sub") - if err != nil { - t.Fatalf("Sub failed: %v", err) - } - - // file.txt should be accessible as "file.txt" not "sub/file.txt" - data, err := fs.ReadFile(subFS, "file.txt") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - if string(data) != "sub file" { - t.Errorf("Content = %q, want %q", data, "sub file") - } - - // root.txt should not be accessible - _, err = fs.ReadFile(subFS, "../root.txt") - if err == nil { - t.Error("Expected error accessing parent directory") - } - }) - - t.Run("Sub nested", func(t *testing.T) { - subFS, _ := iofs.Sub("sub") - deepFS, err := subFS.(fs.SubFS).Sub("deep") - if err != nil { - t.Fatalf("Sub failed: %v", err) - } - - data, err := fs.ReadFile(deepFS, "nested.txt") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - if string(data) != "nested" { - t.Errorf("Content = %q, want %q", data, "nested") - } - }) - - t.Run("Sub non-existent", func(t *testing.T) { - _, err := iofs.Sub("nonexistent") - if err == nil { - t.Error("Expected error") - } - }) - - t.Run("Sub file (not directory)", func(t *testing.T) { - _, err := iofs.Sub("root.txt") - if err == nil { - t.Error("Expected error for Sub on file") - } - }) - - t.Run("Sub dot returns same", func(t *testing.T) { - subFS, err := iofs.Sub(".") - if err != nil { - t.Fatalf("Sub(.) failed: %v", err) - } - if subFS != iofs { - t.Error("Sub(.) should return same FS") - } - }) -} - -func TestIOFS_WithContext(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - afs.FS.WriteFile(ctx, "/test.txt", []byte("test"), 0o644) - - iofs := NewIOFS(afs.FS) - - t.Run("WithContext creates copy", func(t *testing.T) { - newCtx := context.WithValue(ctx, "key", "value") - iofsWithCtx := iofs.WithContext(newCtx) - - if iofsWithCtx == iofs { - t.Error("WithContext should return a new instance") - } - - // Should still work - data, err := iofsWithCtx.ReadFile("test.txt") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - if string(data) != "test" { - t.Errorf("Content = %q, want %q", data, "test") - } - }) -} - -func TestIOFS_WalkDir(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - afs.FS.WriteFile(ctx, "/a.txt", []byte("a"), 0o644) - afs.FS.WriteFile(ctx, "/dir/b.txt", []byte("b"), 0o644) - afs.FS.WriteFile(ctx, "/dir/subdir/c.txt", []byte("c"), 0o644) - - iofs := NewIOFS(afs.FS) - - t.Run("WalkDir visits all", func(t *testing.T) { - var visited []string - err := fs.WalkDir(iofs, ".", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - visited = append(visited, path) - return nil - }) - if err != nil { - t.Fatalf("WalkDir failed: %v", err) - } - - // Should visit: ., a.txt, dir, dir/b.txt, dir/subdir, dir/subdir/c.txt - if len(visited) != 6 { - t.Errorf("Visited %d paths, want 6: %v", len(visited), visited) - } - }) -} - -func TestIOFS_FileMode(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - afs.FS.WriteFile(ctx, "/regular.txt", []byte("x"), 0o644) - afs.FS.MkdirAll(ctx, "/directory", 0o755) - afs.FS.Symlink(ctx, "/regular.txt", "/symlink") - - iofs := NewIOFS(afs.FS) - - t.Run("Regular file mode", func(t *testing.T) { - info, _ := iofs.Stat("regular.txt") - mode := info.Mode() - - if mode.IsDir() { - t.Error("Regular file should not be a directory") - } - if mode&fs.ModeSymlink != 0 { - t.Error("Regular file should not be a symlink") - } - if mode.Perm() != 0o644 { - t.Errorf("Permissions = %o, want %o", mode.Perm(), 0o644) - } - }) - - t.Run("Directory mode", func(t *testing.T) { - info, _ := iofs.Stat("directory") - mode := info.Mode() - - if !mode.IsDir() { - t.Error("Directory should be a directory") - } - if mode&fs.ModeDir == 0 { - t.Error("Mode should have ModeDir bit set") - } - }) - - t.Run("Symlink mode via Stat (follows symlink)", func(t *testing.T) { - info, _ := iofs.Stat("symlink") - mode := info.Mode() - - // Stat follows the symlink, so we should see the target's mode (regular file) - if mode&fs.ModeSymlink != 0 { - t.Error("Stat on symlink should follow to target, not show ModeSymlink") - } - if mode.IsDir() { - t.Error("Symlink target is a regular file, not a directory") - } - }) -} - -func TestIOFS_FSTest(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - // Create test files for fstest.TestFS - afs.FS.WriteFile(ctx, "/hello.txt", []byte("hello"), 0o644) - afs.FS.WriteFile(ctx, "/world.txt", []byte("world"), 0o644) - afs.FS.WriteFile(ctx, "/sub/nested.txt", []byte("nested"), 0o644) - - iofs := NewIOFS(afs.FS) - - // Use the standard library's fstest to validate our implementation - err := fstest.TestFS(iofs, "hello.txt", "world.txt", "sub/nested.txt") - if err != nil { - t.Fatalf("fstest.TestFS failed: %v", err) - } -} - -// Helper function to check for specific path errors -func isPathError(err error, target error) bool { - if pathErr, ok := err.(*fs.PathError); ok { - return pathErr.Err == target - } - return false -} diff --git a/sdk/go/kvstore.go b/sdk/go/kvstore.go deleted file mode 100644 index 30027550..00000000 --- a/sdk/go/kvstore.go +++ /dev/null @@ -1,238 +0,0 @@ -package agentfs - -import ( - "context" - "database/sql" - "encoding/json" - "fmt" - "strings" -) - -// KVStore provides key-value storage backed by SQLite. -type KVStore struct { - db *sql.DB -} - -// Set stores a value (JSON-serialized) for the given key. -func (kv *KVStore) Set(ctx context.Context, key string, value any) error { - jsonValue, err := json.Marshal(value) - if err != nil { - return fmt.Errorf("failed to marshal value: %w", err) - } - - if _, err := kv.db.ExecContext(ctx, kvSet, key, string(jsonValue)); err != nil { - return fmt.Errorf("failed to set key: %w", err) - } - - return nil -} - -// Get retrieves a value and unmarshals it into dest. -// Returns an error if the key does not exist. -func (kv *KVStore) Get(ctx context.Context, key string, dest any) error { - var jsonValue string - err := kv.db.QueryRowContext(ctx, kvGet, key).Scan(&jsonValue) - if err == sql.ErrNoRows { - return fmt.Errorf("key not found: %s", key) - } - if err != nil { - return fmt.Errorf("failed to get key: %w", err) - } - - if err := json.Unmarshal([]byte(jsonValue), dest); err != nil { - return fmt.Errorf("failed to unmarshal value: %w", err) - } - - return nil -} - -// GetRaw retrieves the raw JSON value for a key. -// Returns an error if the key does not exist. -func (kv *KVStore) GetRaw(ctx context.Context, key string) (json.RawMessage, error) { - var jsonValue string - err := kv.db.QueryRowContext(ctx, kvGet, key).Scan(&jsonValue) - if err == sql.ErrNoRows { - return nil, fmt.Errorf("key not found: %s", key) - } - if err != nil { - return nil, fmt.Errorf("failed to get key: %w", err) - } - - return json.RawMessage(jsonValue), nil -} - -// Delete removes a key. -func (kv *KVStore) Delete(ctx context.Context, key string) error { - if _, err := kv.db.ExecContext(ctx, kvDelete, key); err != nil { - return fmt.Errorf("failed to delete key: %w", err) - } - return nil -} - -// Has checks if a key exists. -func (kv *KVStore) Has(ctx context.Context, key string) (bool, error) { - var exists int - err := kv.db.QueryRowContext(ctx, kvHas, key).Scan(&exists) - if err == sql.ErrNoRows { - return false, nil - } - if err != nil { - return false, fmt.Errorf("failed to check key: %w", err) - } - return exists == 1, nil -} - -// Keys returns all keys, optionally filtered by prefix. -// If prefix is empty, all keys are returned. -func (kv *KVStore) Keys(ctx context.Context, prefix string) ([]string, error) { - var rows *sql.Rows - var err error - - if prefix == "" { - rows, err = kv.db.QueryContext(ctx, kvKeys) - } else { - pattern := escapePattern(prefix) + "%" - rows, err = kv.db.QueryContext(ctx, kvKeysWithPrefix, pattern) - } - - if err != nil { - return nil, fmt.Errorf("failed to list keys: %w", err) - } - defer rows.Close() - - var keys []string - for rows.Next() { - var key string - if err := rows.Scan(&key); err != nil { - return nil, err - } - keys = append(keys, key) - } - - return keys, rows.Err() -} - -// List returns all key entries with metadata, optionally filtered by prefix. -func (kv *KVStore) List(ctx context.Context, prefix string) ([]KVEntry, error) { - var rows *sql.Rows - var err error - - if prefix == "" { - rows, err = kv.db.QueryContext(ctx, kvList) - } else { - pattern := escapePattern(prefix) + "%" - rows, err = kv.db.QueryContext(ctx, kvListWithPrefix, pattern) - } - - if err != nil { - return nil, fmt.Errorf("failed to list entries: %w", err) - } - defer rows.Close() - - var entries []KVEntry - for rows.Next() { - var entry KVEntry - if err := rows.Scan(&entry.Key, &entry.CreatedAt, &entry.UpdatedAt); err != nil { - return nil, err - } - entries = append(entries, entry) - } - - return entries, rows.Err() -} - -// Clear removes all keys, optionally filtered by prefix. -func (kv *KVStore) Clear(ctx context.Context, prefix string) error { - var err error - - if prefix == "" { - _, err = kv.db.ExecContext(ctx, kvClear) - } else { - pattern := escapePattern(prefix) + "%" - _, err = kv.db.ExecContext(ctx, kvClearWithPrefix, pattern) - } - - if err != nil { - return fmt.Errorf("failed to clear keys: %w", err) - } - return nil -} - -// escapePattern escapes special characters for LIKE pattern matching -func escapePattern(s string) string { - s = strings.ReplaceAll(s, "\\", "\\\\") - s = strings.ReplaceAll(s, "%", "\\%") - s = strings.ReplaceAll(s, "_", "\\_") - return s -} - -// ============================================================================ -// Generic Helper Functions (Go 1.18+) -// ============================================================================ - -// KVGet retrieves a value with type safety using generics. -// Returns the value directly instead of requiring a destination pointer. -// -// Example: -// -// version, err := agentfs.KVGet[string](ctx, afs.KV, "config:version") -// count, err := agentfs.KVGet[int](ctx, afs.KV, "stats:count") -// -// type Config struct { -// Debug bool `json:"debug"` -// } -// cfg, err := agentfs.KVGet[Config](ctx, afs.KV, "app:config") -func KVGet[T any](ctx context.Context, kv *KVStore, key string) (T, error) { - var result T - err := kv.Get(ctx, key, &result) - return result, err -} - -// KVGetOrDefault retrieves a value, returning the default if the key doesn't exist. -// Only returns an error for non-"not found" errors (e.g., JSON unmarshal failures). -// -// Example: -// -// debug := agentfs.KVGetOrDefault(ctx, afs.KV, "config:debug", false) -// name := agentfs.KVGetOrDefault(ctx, afs.KV, "user:name", "anonymous") -func KVGetOrDefault[T any](ctx context.Context, kv *KVStore, key string, defaultValue T) (T, error) { - result, err := KVGet[T](ctx, kv, key) - if err != nil { - // Check if it's a "not found" error - if isKeyNotFoundError(err) { - return defaultValue, nil - } - return defaultValue, err - } - return result, nil -} - -// KVGetOrZero retrieves a value, returning the zero value if the key doesn't exist. -// This is a convenience wrapper around KVGetOrDefault with the zero value. -// -// Example: -// -// count, _ := agentfs.KVGetOrZero[int](ctx, afs.KV, "stats:count") // 0 if not found -// name, _ := agentfs.KVGetOrZero[string](ctx, afs.KV, "user:name") // "" if not found -func KVGetOrZero[T any](ctx context.Context, kv *KVStore, key string) (T, error) { - var zero T - return KVGetOrDefault(ctx, kv, key, zero) -} - -// KVSet is a generic-friendly wrapper around KVStore.Set. -// It's provided for API consistency with the other KV generic functions. -// -// Example: -// -// agentfs.KVSet(ctx, afs.KV, "config:version", "1.0.0") -func KVSet[T any](ctx context.Context, kv *KVStore, key string, value T) error { - return kv.Set(ctx, key, value) -} - -// isKeyNotFoundError checks if an error is a "key not found" error -func isKeyNotFoundError(err error) bool { - if err == nil { - return false - } - return strings.HasPrefix(err.Error(), "key not found:") -} diff --git a/sdk/go/kvstore_generic_test.go b/sdk/go/kvstore_generic_test.go deleted file mode 100644 index c7b786b7..00000000 --- a/sdk/go/kvstore_generic_test.go +++ /dev/null @@ -1,360 +0,0 @@ -package agentfs - -import ( - "context" - "testing" -) - -func TestKVGet_Generic(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - t.Run("Get string", func(t *testing.T) { - afs.KV.Set(ctx, "str", "hello") - - val, err := KVGet[string](ctx, afs.KV, "str") - if err != nil { - t.Fatalf("KVGet failed: %v", err) - } - if val != "hello" { - t.Errorf("val = %q, want %q", val, "hello") - } - }) - - t.Run("Get int", func(t *testing.T) { - afs.KV.Set(ctx, "num", 42) - - val, err := KVGet[int](ctx, afs.KV, "num") - if err != nil { - t.Fatalf("KVGet failed: %v", err) - } - if val != 42 { - t.Errorf("val = %d, want %d", val, 42) - } - }) - - t.Run("Get float", func(t *testing.T) { - afs.KV.Set(ctx, "float", 3.14) - - val, err := KVGet[float64](ctx, afs.KV, "float") - if err != nil { - t.Fatalf("KVGet failed: %v", err) - } - if val != 3.14 { - t.Errorf("val = %f, want %f", val, 3.14) - } - }) - - t.Run("Get bool", func(t *testing.T) { - afs.KV.Set(ctx, "flag", true) - - val, err := KVGet[bool](ctx, afs.KV, "flag") - if err != nil { - t.Fatalf("KVGet failed: %v", err) - } - if val != true { - t.Errorf("val = %v, want %v", val, true) - } - }) - - t.Run("Get struct", func(t *testing.T) { - type Config struct { - Name string `json:"name"` - Version int `json:"version"` - Debug bool `json:"debug"` - } - - input := Config{Name: "myapp", Version: 2, Debug: true} - afs.KV.Set(ctx, "config", input) - - val, err := KVGet[Config](ctx, afs.KV, "config") - if err != nil { - t.Fatalf("KVGet failed: %v", err) - } - if val != input { - t.Errorf("val = %+v, want %+v", val, input) - } - }) - - t.Run("Get slice", func(t *testing.T) { - input := []string{"a", "b", "c"} - afs.KV.Set(ctx, "slice", input) - - val, err := KVGet[[]string](ctx, afs.KV, "slice") - if err != nil { - t.Fatalf("KVGet failed: %v", err) - } - if len(val) != 3 || val[0] != "a" || val[1] != "b" || val[2] != "c" { - t.Errorf("val = %v, want %v", val, input) - } - }) - - t.Run("Get map", func(t *testing.T) { - input := map[string]int{"a": 1, "b": 2} - afs.KV.Set(ctx, "map", input) - - val, err := KVGet[map[string]int](ctx, afs.KV, "map") - if err != nil { - t.Fatalf("KVGet failed: %v", err) - } - if val["a"] != 1 || val["b"] != 2 { - t.Errorf("val = %v, want %v", val, input) - } - }) - - t.Run("Get non-existent key", func(t *testing.T) { - _, err := KVGet[string](ctx, afs.KV, "nonexistent") - if err == nil { - t.Error("Expected error for non-existent key") - } - }) - - t.Run("Get with type mismatch", func(t *testing.T) { - afs.KV.Set(ctx, "string_val", "not a number") - - // Try to get string as int - should fail during unmarshal - _, err := KVGet[int](ctx, afs.KV, "string_val") - if err == nil { - t.Error("Expected error for type mismatch") - } - }) -} - -func TestKVGetOrDefault_Generic(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - t.Run("Returns value when exists", func(t *testing.T) { - afs.KV.Set(ctx, "exists", "found") - - val, err := KVGetOrDefault(ctx, afs.KV, "exists", "default") - if err != nil { - t.Fatalf("KVGetOrDefault failed: %v", err) - } - if val != "found" { - t.Errorf("val = %q, want %q", val, "found") - } - }) - - t.Run("Returns default when not exists", func(t *testing.T) { - val, err := KVGetOrDefault(ctx, afs.KV, "not_exists", "default_value") - if err != nil { - t.Fatalf("KVGetOrDefault failed: %v", err) - } - if val != "default_value" { - t.Errorf("val = %q, want %q", val, "default_value") - } - }) - - t.Run("Returns default int", func(t *testing.T) { - val, err := KVGetOrDefault(ctx, afs.KV, "missing_int", 100) - if err != nil { - t.Fatalf("KVGetOrDefault failed: %v", err) - } - if val != 100 { - t.Errorf("val = %d, want %d", val, 100) - } - }) - - t.Run("Returns default bool", func(t *testing.T) { - val, err := KVGetOrDefault(ctx, afs.KV, "missing_bool", true) - if err != nil { - t.Fatalf("KVGetOrDefault failed: %v", err) - } - if val != true { - t.Errorf("val = %v, want %v", val, true) - } - }) - - t.Run("Returns default struct", func(t *testing.T) { - type Settings struct { - Theme string `json:"theme"` - } - - defaultSettings := Settings{Theme: "dark"} - val, err := KVGetOrDefault(ctx, afs.KV, "missing_settings", defaultSettings) - if err != nil { - t.Fatalf("KVGetOrDefault failed: %v", err) - } - if val != defaultSettings { - t.Errorf("val = %+v, want %+v", val, defaultSettings) - } - }) - - t.Run("Returns error on type mismatch", func(t *testing.T) { - afs.KV.Set(ctx, "string_for_int", "not an int") - - _, err := KVGetOrDefault(ctx, afs.KV, "string_for_int", 0) - if err == nil { - t.Error("Expected error for type mismatch") - } - }) -} - -func TestKVGetOrZero_Generic(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - t.Run("Returns value when exists", func(t *testing.T) { - afs.KV.Set(ctx, "has_value", 42) - - val, err := KVGetOrZero[int](ctx, afs.KV, "has_value") - if err != nil { - t.Fatalf("KVGetOrZero failed: %v", err) - } - if val != 42 { - t.Errorf("val = %d, want %d", val, 42) - } - }) - - t.Run("Returns zero int", func(t *testing.T) { - val, err := KVGetOrZero[int](ctx, afs.KV, "missing") - if err != nil { - t.Fatalf("KVGetOrZero failed: %v", err) - } - if val != 0 { - t.Errorf("val = %d, want %d", val, 0) - } - }) - - t.Run("Returns zero string", func(t *testing.T) { - val, err := KVGetOrZero[string](ctx, afs.KV, "missing") - if err != nil { - t.Fatalf("KVGetOrZero failed: %v", err) - } - if val != "" { - t.Errorf("val = %q, want %q", val, "") - } - }) - - t.Run("Returns zero bool", func(t *testing.T) { - val, err := KVGetOrZero[bool](ctx, afs.KV, "missing") - if err != nil { - t.Fatalf("KVGetOrZero failed: %v", err) - } - if val != false { - t.Errorf("val = %v, want %v", val, false) - } - }) - - t.Run("Returns nil slice", func(t *testing.T) { - val, err := KVGetOrZero[[]string](ctx, afs.KV, "missing") - if err != nil { - t.Fatalf("KVGetOrZero failed: %v", err) - } - if val != nil { - t.Errorf("val = %v, want nil", val) - } - }) -} - -func TestKVSet_Generic(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - t.Run("Set and get string", func(t *testing.T) { - err := KVSet(ctx, afs.KV, "gen_str", "generic string") - if err != nil { - t.Fatalf("KVSet failed: %v", err) - } - - val, _ := KVGet[string](ctx, afs.KV, "gen_str") - if val != "generic string" { - t.Errorf("val = %q, want %q", val, "generic string") - } - }) - - t.Run("Set and get struct", func(t *testing.T) { - type User struct { - ID int `json:"id"` - Name string `json:"name"` - } - - user := User{ID: 1, Name: "Alice"} - err := KVSet(ctx, afs.KV, "user", user) - if err != nil { - t.Fatalf("KVSet failed: %v", err) - } - - val, _ := KVGet[User](ctx, afs.KV, "user") - if val != user { - t.Errorf("val = %+v, want %+v", val, user) - } - }) -} - -func TestKVGeneric_RealWorldExample(t *testing.T) { - ctx := context.Background() - afs := setupTestDB(t) - defer afs.Close() - - // Simulate a real-world usage pattern - - type AppConfig struct { - Debug bool `json:"debug"` - LogLevel string `json:"log_level"` - MaxRetries int `json:"max_retries"` - AllowedTags []string `json:"allowed_tags"` - } - - t.Run("Application config workflow", func(t *testing.T) { - // Set default config - defaultConfig := AppConfig{ - Debug: false, - LogLevel: "info", - MaxRetries: 3, - AllowedTags: []string{"production"}, - } - - // Try to get config, use default if not exists - config, err := KVGetOrDefault(ctx, afs.KV, "app:config", defaultConfig) - if err != nil { - t.Fatalf("Failed to get config: %v", err) - } - - // Should be the default - if config.LogLevel != "info" { - t.Errorf("LogLevel = %q, want %q", config.LogLevel, "info") - } - - // Update config - config.Debug = true - config.LogLevel = "debug" - err = KVSet(ctx, afs.KV, "app:config", config) - if err != nil { - t.Fatalf("Failed to set config: %v", err) - } - - // Retrieve updated config - updated, err := KVGet[AppConfig](ctx, afs.KV, "app:config") - if err != nil { - t.Fatalf("Failed to get updated config: %v", err) - } - - if !updated.Debug { - t.Error("Debug should be true") - } - if updated.LogLevel != "debug" { - t.Errorf("LogLevel = %q, want %q", updated.LogLevel, "debug") - } - }) - - t.Run("Counter pattern", func(t *testing.T) { - // Get current count (default 0) - count, _ := KVGetOrZero[int](ctx, afs.KV, "stats:requests") - - // Increment - count++ - KVSet(ctx, afs.KV, "stats:requests", count) - - // Verify - newCount, _ := KVGet[int](ctx, afs.KV, "stats:requests") - if newCount != 1 { - t.Errorf("count = %d, want 1", newCount) - } - }) -} diff --git a/sdk/go/overlay.go b/sdk/go/overlay.go deleted file mode 100644 index b662566d..00000000 --- a/sdk/go/overlay.go +++ /dev/null @@ -1,1414 +0,0 @@ -package agentfs - -import ( - "context" - "database/sql" - "path" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/tursodatabase/agentfs/sdk/go/internal/cache" -) - -// Layer indicates which layer an inode belongs to in an overlay filesystem. -type Layer int - -const ( - // LayerDelta is the writable upper layer where modifications are stored. - LayerDelta Layer = iota - // LayerBase is the read-only lower layer. - LayerBase -) - -// InodeInfo tracks information about an inode in the overlay filesystem. -type InodeInfo struct { - Layer Layer - UnderlyingIno int64 - Path string -} - -// BaseFS is the interface that the base (read-only) filesystem must implement. -// This allows using any filesystem implementation as the base layer. -type BaseFS interface { - // Stat returns file/directory metadata for the given inode. - Stat(ctx context.Context, ino int64) (*Stats, error) - - // Lookup finds an entry in a directory by name. - Lookup(ctx context.Context, parentIno int64, name string) (*Stats, error) - - // Readdir returns the names of entries in a directory. - Readdir(ctx context.Context, ino int64) ([]string, error) - - // ReaddirPlus returns directory entries with their stats. - ReaddirPlus(ctx context.Context, ino int64) ([]DirEntry, error) - - // ReadFile reads the entire contents of a file. - ReadFile(ctx context.Context, ino int64) ([]byte, error) - - // Readlink returns the target of a symbolic link. - Readlink(ctx context.Context, ino int64) (string, error) -} - -// OverlayCacheOptions configures the LRU path cache for OverlayFS. -type OverlayCacheOptions struct { - // Enabled enables the LRU path cache. - Enabled bool - // MaxEntries is the maximum number of path->inode entries to cache. - // Defaults to 10000 if not specified. - MaxEntries int - // TTL is the time-to-live for cache entries. 0 means no expiration. - TTL time.Duration -} - -// OverlayFS provides a copy-on-write overlay filesystem. -// It combines a read-only base layer with a writable delta layer (AgentFS). -// All modifications are written to the delta layer, while reads fall back -// to the base layer if not found in delta. -type OverlayFS struct { - base BaseFS - delta *Filesystem - db *sql.DB - - // Inode mapping: overlay_ino -> InodeInfo - inodeMap sync.Map // map[int64]InodeInfo - - // Reverse mapping: (layer, underlying_ino) -> overlay_ino - reverseMap sync.Map // map[layerInoKey]int64 - - // Path to overlay inode mapping (unbounded, used for inode stability) - pathMap sync.Map // map[string]int64 - - // LRU cache for path resolution (optional, bounded) - cache cache.PathCache - - // Whiteout paths (deleted from base) - whiteouts sync.Map // map[string]bool - - // Origin mapping: delta_ino -> base_ino - originMap sync.Map // map[int64]int64 - - // Next inode number to allocate - nextIno atomic.Int64 -} - -// layerInoKey is a key for the reverse map. -type layerInoKey struct { - layer Layer - ino int64 -} - -// NewOverlayFS creates a new overlay filesystem. -// The base layer is read-only, and the delta layer receives all modifications. -func NewOverlayFS(base BaseFS, delta *Filesystem, db *sql.DB) *OverlayFS { - return NewOverlayFSWithCache(base, delta, db, OverlayCacheOptions{}) -} - -// NewOverlayFSWithCache creates a new overlay filesystem with optional LRU caching. -// The base layer is read-only, and the delta layer receives all modifications. -func NewOverlayFSWithCache(base BaseFS, delta *Filesystem, db *sql.DB, cacheOpts OverlayCacheOptions) *OverlayFS { - ofs := &OverlayFS{ - base: base, - delta: delta, - db: db, - } - - // Initialize LRU cache if enabled - if cacheOpts.Enabled { - maxEntries := cacheOpts.MaxEntries - if maxEntries <= 0 { - maxEntries = 10000 - } - pathCache, err := cache.NewLRU(maxEntries, cacheOpts.TTL) - if err == nil { - ofs.cache = pathCache - } - } - - // Initialize root inode mapping (root is always inode 1 in both layers) - ofs.inodeMap.Store(int64(RootIno), InodeInfo{ - Layer: LayerDelta, - UnderlyingIno: RootIno, - Path: "/", - }) - ofs.reverseMap.Store(layerInoKey{LayerDelta, RootIno}, int64(RootIno)) - ofs.pathMap.Store("/", int64(RootIno)) - - // Start allocating inodes from 2 - ofs.nextIno.Store(2) - - return ofs -} - -// Init initializes the overlay filesystem by loading persisted state. -// Call this after creating an OverlayFS for an existing database. -func (ofs *OverlayFS) Init(ctx context.Context) error { - if err := ofs.loadWhiteouts(ctx); err != nil { - return err - } - if err := ofs.loadOrigins(ctx); err != nil { - return err - } - return nil -} - -// Base returns the base filesystem. -func (ofs *OverlayFS) Base() BaseFS { - return ofs.base -} - -// Delta returns the delta filesystem. -func (ofs *OverlayFS) Delta() *Filesystem { - return ofs.delta -} - -// allocIno allocates a new overlay inode number. -func (ofs *OverlayFS) allocIno() int64 { - return ofs.nextIno.Add(1) - 1 -} - -// getOrCreateOverlayIno gets or creates an overlay inode for a layer inode. -func (ofs *OverlayFS) getOrCreateOverlayIno(layer Layer, underlyingIno int64, path string) int64 { - key := layerInoKey{layer, underlyingIno} - - // Check reverse map first - if ino, ok := ofs.reverseMap.Load(key); ok { - return ino.(int64) - } - - // Allocate new inode - ino := ofs.allocIno() - - ofs.inodeMap.Store(ino, InodeInfo{ - Layer: layer, - UnderlyingIno: underlyingIno, - Path: path, - }) - ofs.reverseMap.Store(key, ino) - ofs.pathMap.Store(path, ino) - - return ino -} - -// getOrCreateOverlayInoWithOrigin gets or creates an overlay inode for a file that was copied up. -// Uses the base inode for stable numbering (kernel cache consistency) but maps to delta for data. -// This ensures that after copy-up, Stat returns delta data (with modifications) but the inode -// number remains stable across the copy-up operation. -func (ofs *OverlayFS) getOrCreateOverlayInoWithOrigin(baseIno, deltaIno int64, path string) int64 { - // Check if we already have an overlay inode for this base inode - key := layerInoKey{LayerBase, baseIno} - if ino, ok := ofs.reverseMap.Load(key); ok { - overlayIno := ino.(int64) - // Update the mapping to point to delta layer for data access - ofs.inodeMap.Store(overlayIno, InodeInfo{ - Layer: LayerDelta, - UnderlyingIno: deltaIno, - Path: path, - }) - // Also add delta to reverse map - ofs.reverseMap.Store(layerInoKey{LayerDelta, deltaIno}, overlayIno) - return overlayIno - } - - // Allocate new inode - ino := ofs.allocIno() - - // Map overlay inode to delta layer (for reading actual data after copy-up) - ofs.inodeMap.Store(ino, InodeInfo{ - Layer: LayerDelta, - UnderlyingIno: deltaIno, - Path: path, - }) - - // Store reverse mappings for both base and delta - // Base key ensures stable inode for files that existed in base - ofs.reverseMap.Store(key, ino) - ofs.reverseMap.Store(layerInoKey{LayerDelta, deltaIno}, ino) - ofs.pathMap.Store(path, ino) - - return ino -} - -// getInodeInfo returns inode info for an overlay inode. -func (ofs *OverlayFS) getInodeInfo(ino int64) (InodeInfo, bool) { - if info, ok := ofs.inodeMap.Load(ino); ok { - return info.(InodeInfo), true - } - return InodeInfo{}, false -} - -// buildPath builds a path from parent inode and name. -func (ofs *OverlayFS) buildPath(parentIno int64, name string) (string, error) { - info, ok := ofs.getInodeInfo(parentIno) - if !ok { - return "", ErrNoent("buildPath", "") - } - if info.Path == "/" { - return "/" + name, nil - } - return info.Path + "/" + name, nil -} - -// parentPath returns the parent path of a given path. -func parentPath(p string) string { - if p == "/" { - return "/" - } - parent := path.Dir(p) - if parent == "." { - return "/" - } - return parent -} - -// ============================================================================= -// Whiteout Operations -// ============================================================================= - -// loadWhiteouts loads whiteouts from the database into memory. -func (ofs *OverlayFS) loadWhiteouts(ctx context.Context) error { - rows, err := ofs.db.QueryContext(ctx, whiteoutList) - if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var path string - if err := rows.Scan(&path); err != nil { - return err - } - ofs.whiteouts.Store(path, true) - } - return rows.Err() -} - -// createWhiteout creates a whiteout for a path. -func (ofs *OverlayFS) createWhiteout(ctx context.Context, p string) error { - now := time.Now().Unix() - parent := parentPath(p) - - _, err := ofs.db.ExecContext(ctx, whiteoutInsert, p, parent, now) - if err != nil { - return err - } - ofs.whiteouts.Store(p, true) - - // Invalidate cache - path is now hidden - ofs.invalidateCachePrefix(p) - - return nil -} - -// removeWhiteout removes a whiteout for a path. -func (ofs *OverlayFS) removeWhiteout(ctx context.Context, p string) error { - if _, ok := ofs.whiteouts.Load(p); !ok { - return nil // Not a whiteout - } - - _, err := ofs.db.ExecContext(ctx, whiteoutDelete, p) - if err != nil { - return err - } - ofs.whiteouts.Delete(p) - - // Invalidate cache - path visibility changed - ofs.invalidateCachePrefix(p) - - return nil -} - -// isWhiteout checks if a path or any of its ancestors is whiteout. -func (ofs *OverlayFS) isWhiteout(p string) bool { - // Check path and all ancestors - current := "" - for _, component := range strings.Split(p, "/") { - if component == "" { - continue - } - current = current + "/" + component - if _, ok := ofs.whiteouts.Load(current); ok { - return true - } - } - return false -} - -// getChildWhiteouts returns the names of whiteout entries that are direct children of a directory. -func (ofs *OverlayFS) getChildWhiteouts(dirPath string) map[string]bool { - result := make(map[string]bool) - - prefix := dirPath - if prefix != "/" { - prefix = prefix + "/" - } - - ofs.whiteouts.Range(func(key, value interface{}) bool { - p := key.(string) - if dirPath == "/" { - // Direct children of root - trimmed := strings.TrimPrefix(p, "/") - if !strings.Contains(trimmed, "/") && trimmed != "" { - result[trimmed] = true - } - } else if strings.HasPrefix(p, prefix) { - rest := p[len(prefix):] - if !strings.Contains(rest, "/") && rest != "" { - result[rest] = true - } - } - return true - }) - - return result -} - -// ============================================================================= -// Origin Tracking -// ============================================================================= - -// loadOrigins loads origin mappings from the database. -func (ofs *OverlayFS) loadOrigins(ctx context.Context) error { - rows, err := ofs.db.QueryContext(ctx, originList) - if err != nil { - // Table might not exist in older databases - return nil - } - defer rows.Close() - - for rows.Next() { - var deltaIno, baseIno int64 - if err := rows.Scan(&deltaIno, &baseIno); err != nil { - return err - } - ofs.originMap.Store(deltaIno, baseIno) - } - return rows.Err() -} - -// addOriginMapping stores an origin mapping for copy-up. -func (ofs *OverlayFS) addOriginMapping(ctx context.Context, deltaIno, baseIno int64) error { - _, err := ofs.db.ExecContext(ctx, originInsert, deltaIno, baseIno) - if err != nil { - return err - } - ofs.originMap.Store(deltaIno, baseIno) - return nil -} - -// getOriginIno gets the base inode for a delta inode if it was copied up. -func (ofs *OverlayFS) getOriginIno(deltaIno int64) (int64, bool) { - if baseIno, ok := ofs.originMap.Load(deltaIno); ok { - return baseIno.(int64), true - } - return 0, false -} - -// ============================================================================= -// Copy-Up Operations -// ============================================================================= - -// ensureParentDirs ensures parent directories exist in the delta layer. -func (ofs *OverlayFS) ensureParentDirs(ctx context.Context, p string, uid, gid int64) error { - components := strings.Split(strings.Trim(p, "/"), "/") - if len(components) <= 1 { - return nil // No parent directories to create - } - - currentPath := "" - currentDeltaIno := int64(RootIno) - currentBaseIno := int64(RootIno) - - // Process all but the last component (the file/dir being created) - for _, component := range components[:len(components)-1] { - currentPath = currentPath + "/" + component - - // Remove any whiteout for this path - if err := ofs.removeWhiteout(ctx, currentPath); err != nil { - return err - } - - // Check if directory exists in delta - deltaStats, err := ofs.delta.lookupDentry(ctx, currentDeltaIno, component) - if err == nil { - // Exists in delta - stats, err := ofs.delta.statInode(ctx, deltaStats) - if err != nil { - return err - } - if !stats.IsDir() { - return ErrNotDir("ensureParentDirs", currentPath) - } - currentDeltaIno = deltaStats - - // Advance base in parallel - if baseStats, err := ofs.base.Lookup(ctx, currentBaseIno, component); err == nil && baseStats != nil { - currentBaseIno = baseStats.Ino - } - continue - } - - // Not in delta, check base - var dirUID, dirGID int64 - var originBaseIno int64 - baseStats, err := ofs.base.Lookup(ctx, currentBaseIno, component) - if err == nil && baseStats != nil { - dirUID = baseStats.UID - dirGID = baseStats.GID - originBaseIno = baseStats.Ino - currentBaseIno = baseStats.Ino - } else { - dirUID = uid - dirGID = gid - } - - // Create directory in delta - now := time.Now() - nowSec := now.Unix() - nowNsec := int64(now.Nanosecond()) - dirMode := S_IFDIR | 0o755 - - var newIno int64 - err = ofs.db.QueryRowContext(ctx, insertInode, dirMode, dirUID, dirGID, 0, nowSec, nowSec, nowSec, 0, nowNsec, nowNsec, nowNsec).Scan(&newIno) - if err != nil { - return err - } - - if _, err := ofs.db.ExecContext(ctx, insertDentry, component, currentDeltaIno, newIno); err != nil { - return err - } - if _, err := ofs.db.ExecContext(ctx, incrementNlink, newIno); err != nil { - return err - } - - // Create origin mapping if directory exists in base - if originBaseIno != 0 { - if err := ofs.addOriginMapping(ctx, newIno, originBaseIno); err != nil { - return err - } - } - - currentDeltaIno = newIno - } - - return nil -} - -// copyUp copies a file from base to delta for modification. -// Returns the delta inode number. -func (ofs *OverlayFS) copyUp(ctx context.Context, p string, baseIno int64) (int64, error) { - components := strings.Split(strings.Trim(p, "/"), "/") - if len(components) == 0 { - return 0, ErrPerm("copyUp", "/") - } - name := components[len(components)-1] - - // Check if already copied up - parentIno := int64(RootIno) - foundParent := true - for _, comp := range components[:len(components)-1] { - ino, err := ofs.delta.lookupDentry(ctx, parentIno, comp) - if err != nil { - foundParent = false - break - } - parentIno = ino - } - - if foundParent { - if ino, err := ofs.delta.lookupDentry(ctx, parentIno, name); err == nil { - // Already copied up - return ino, nil - } - } - - // Get base stats - baseStats, err := ofs.base.Stat(ctx, baseIno) - if err != nil { - return 0, err - } - - // Ensure parent directories exist - if err := ofs.ensureParentDirs(ctx, p, baseStats.UID, baseStats.GID); err != nil { - return 0, err - } - - // Look up parent in delta by walking the path - parentIno = RootIno - for _, comp := range components[:len(components)-1] { - ino, err := ofs.delta.lookupDentry(ctx, parentIno, comp) - if err != nil { - return 0, ErrNoent("copyUp", p) - } - parentIno = ino - } - - now := time.Now() - nowSec := now.Unix() - nowNsec := int64(now.Nanosecond()) - - var deltaIno int64 - - if baseStats.IsSymlink() { - // Copy symlink - target, err := ofs.base.Readlink(ctx, baseIno) - if err != nil { - return 0, err - } - - err = ofs.db.QueryRowContext(ctx, insertInode, S_IFLNK|0o777, baseStats.UID, baseStats.GID, len(target), nowSec, nowSec, nowSec, 0, nowNsec, nowNsec, nowNsec).Scan(&deltaIno) - if err != nil { - return 0, err - } - - if _, err := ofs.db.ExecContext(ctx, insertSymlink, deltaIno, target); err != nil { - return 0, err - } - } else if baseStats.IsDir() { - // Copy directory (structure only, not contents) - err = ofs.db.QueryRowContext(ctx, insertInode, baseStats.Mode, baseStats.UID, baseStats.GID, 0, nowSec, nowSec, nowSec, 0, nowNsec, nowNsec, nowNsec).Scan(&deltaIno) - if err != nil { - return 0, err - } - } else { - // Copy regular file - content, err := ofs.base.ReadFile(ctx, baseIno) - if err != nil { - return 0, err - } - - err = ofs.db.QueryRowContext(ctx, insertInode, baseStats.Mode, baseStats.UID, baseStats.GID, len(content), nowSec, nowSec, nowSec, 0, nowNsec, nowNsec, nowNsec).Scan(&deltaIno) - if err != nil { - return 0, err - } - - // Write data chunks - if err := ofs.delta.writeChunks(ctx, deltaIno, content); err != nil { - return 0, err - } - } - - // Create dentry - if _, err := ofs.db.ExecContext(ctx, insertDentry, name, parentIno, deltaIno); err != nil { - return 0, err - } - if _, err := ofs.db.ExecContext(ctx, incrementNlink, deltaIno); err != nil { - return 0, err - } - - // Store origin mapping - if err := ofs.addOriginMapping(ctx, deltaIno, baseIno); err != nil { - return 0, err - } - - // Invalidate cache - inode mapping changed from base to delta - ofs.invalidateCache(p) - - return deltaIno, nil -} - -// copyUpAndUpdateMapping copies up a file and updates the inode mapping. -func (ofs *OverlayFS) copyUpAndUpdateMapping(ctx context.Context, overlayIno int64, info InodeInfo) (int64, error) { - deltaIno, err := ofs.copyUp(ctx, info.Path, info.UnderlyingIno) - if err != nil { - return 0, err - } - - // Update the inode mapping to point to delta - ofs.inodeMap.Store(overlayIno, InodeInfo{ - Layer: LayerDelta, - UnderlyingIno: deltaIno, - Path: info.Path, - }) - - // Add delta to reverse map (keep base mapping for origin lookups) - ofs.reverseMap.Store(layerInoKey{LayerDelta, deltaIno}, overlayIno) - - return deltaIno, nil -} - -// ============================================================================= -// Overlay Filesystem Operations -// ============================================================================= - -// Lookup finds an entry in a directory by name. -func (ofs *OverlayFS) Lookup(ctx context.Context, parentIno int64, name string) (*Stats, error) { - parentInfo, ok := ofs.getInodeInfo(parentIno) - if !ok { - return nil, ErrNoent("lookup", "") - } - - p, err := ofs.buildPath(parentIno, name) - if err != nil { - return nil, err - } - - // Check for whiteout - if ofs.isWhiteout(p) { - return nil, ErrNoent("lookup", p) - } - - // Try delta first - var deltaParentIno int64 - if parentInfo.Layer == LayerDelta { - deltaParentIno = parentInfo.UnderlyingIno - } else { - // Walk delta to find corresponding parent - deltaParentIno = RootIno - for _, comp := range strings.Split(strings.Trim(parentInfo.Path, "/"), "/") { - if comp == "" { - continue - } - ino, err := ofs.delta.lookupDentry(ctx, deltaParentIno, comp) - if err != nil { - deltaParentIno = 0 - break - } - deltaParentIno = ino - } - } - - if deltaParentIno != 0 { - if deltaChildIno, err := ofs.delta.lookupDentry(ctx, deltaParentIno, name); err == nil { - deltaStats, err := ofs.delta.statInode(ctx, deltaChildIno) - if err != nil { - return nil, err - } - - // Check for origin mapping to return stable inode - // We return delta stats (for correct data) but with stable overlay inode - if baseIno, ok := ofs.getOriginIno(deltaStats.Ino); ok { - // Get or create overlay inode for this path - // Use base inode as key for stable numbering, but map to delta for data - overlayIno := ofs.getOrCreateOverlayInoWithOrigin(baseIno, deltaChildIno, p) - deltaStats.Ino = overlayIno - } else { - deltaStats.Ino = ofs.getOrCreateOverlayIno(LayerDelta, deltaStats.Ino, p) - } - - return deltaStats, nil - } - } - - // Try base - var baseParentIno int64 - if parentInfo.Layer == LayerBase { - baseParentIno = parentInfo.UnderlyingIno - } else { - // Walk base to find corresponding parent - baseParentIno = RootIno - for _, comp := range strings.Split(strings.Trim(parentInfo.Path, "/"), "/") { - if comp == "" { - continue - } - stats, err := ofs.base.Lookup(ctx, baseParentIno, comp) - if err != nil || stats == nil { - return nil, ErrNoent("lookup", p) - } - baseParentIno = stats.Ino - } - } - - baseStats, err := ofs.base.Lookup(ctx, baseParentIno, name) - if err != nil || baseStats == nil { - return nil, ErrNoent("lookup", p) - } - - baseStats.Ino = ofs.getOrCreateOverlayIno(LayerBase, baseStats.Ino, p) - return baseStats, nil -} - -// Stat returns file/directory metadata for the given overlay inode. -func (ofs *OverlayFS) Stat(ctx context.Context, ino int64) (*Stats, error) { - info, ok := ofs.getInodeInfo(ino) - if !ok { - return nil, ErrNoent("stat", "") - } - - var stats *Stats - var err error - - switch info.Layer { - case LayerDelta: - stats, err = ofs.delta.statInode(ctx, info.UnderlyingIno) - case LayerBase: - stats, err = ofs.base.Stat(ctx, info.UnderlyingIno) - } - - if err != nil { - return nil, err - } - - stats.Ino = ino - return stats, nil -} - -// Readdir returns the names of entries in a directory. -func (ofs *OverlayFS) Readdir(ctx context.Context, ino int64) ([]string, error) { - info, ok := ofs.getInodeInfo(ino) - if !ok { - return nil, ErrNoent("readdir", "") - } - - childWhiteouts := ofs.getChildWhiteouts(info.Path) - entries := make(map[string]bool) - - // Get delta entries - if info.Layer == LayerDelta { - deltaEntries, err := ofs.delta.Readdir(ctx, info.Path) - if err == nil { - for _, name := range deltaEntries { - entries[name] = true - } - } - } - - // Get base entries - var baseIno int64 - if info.Layer == LayerBase { - baseIno = info.UnderlyingIno - } else { - // Walk base to find corresponding directory - baseIno = RootIno - foundAll := true - for _, comp := range strings.Split(strings.Trim(info.Path, "/"), "/") { - if comp == "" { - continue - } - stats, err := ofs.base.Lookup(ctx, baseIno, comp) - if err != nil || stats == nil { - foundAll = false - break - } - baseIno = stats.Ino - } - if !foundAll { - baseIno = 0 - } - } - - if baseIno != 0 { - baseEntries, err := ofs.base.Readdir(ctx, baseIno) - if err == nil { - for _, name := range baseEntries { - entryPath := info.Path - if entryPath == "/" { - entryPath = "/" + name - } else { - entryPath = entryPath + "/" + name - } - if !ofs.isWhiteout(entryPath) && !childWhiteouts[name] { - entries[name] = true - } - } - } - } - - result := make([]string, 0, len(entries)) - for name := range entries { - result = append(result, name) - } - return result, nil -} - -// ReaddirPlus returns directory entries with their stats. -func (ofs *OverlayFS) ReaddirPlus(ctx context.Context, ino int64) ([]DirEntry, error) { - info, ok := ofs.getInodeInfo(ino) - if !ok { - return nil, ErrNoent("readdirplus", "") - } - - childWhiteouts := ofs.getChildWhiteouts(info.Path) - entriesMap := make(map[string]DirEntry) - - // Get base entries first (so delta can override) - var baseIno int64 - if info.Layer == LayerBase { - baseIno = info.UnderlyingIno - } else { - baseIno = RootIno - foundAll := true - for _, comp := range strings.Split(strings.Trim(info.Path, "/"), "/") { - if comp == "" { - continue - } - stats, err := ofs.base.Lookup(ctx, baseIno, comp) - if err != nil || stats == nil { - foundAll = false - break - } - baseIno = stats.Ino - } - if !foundAll { - baseIno = 0 - } - } - - if baseIno != 0 { - baseEntries, err := ofs.base.ReaddirPlus(ctx, baseIno) - if err == nil { - for _, entry := range baseEntries { - entryPath := info.Path - if entryPath == "/" { - entryPath = "/" + entry.Name - } else { - entryPath = entryPath + "/" + entry.Name - } - - if !ofs.isWhiteout(entryPath) && !childWhiteouts[entry.Name] { - overlayIno := ofs.getOrCreateOverlayIno(LayerBase, entry.Stats.Ino, entryPath) - entry.Stats.Ino = overlayIno - entriesMap[entry.Name] = entry - } - } - } - } - - // Get delta entries (override base) - if info.Layer == LayerDelta { - deltaEntries, err := ofs.delta.ReaddirPlus(ctx, info.Path) - if err == nil { - for _, entry := range deltaEntries { - entryPath := info.Path - if entryPath == "/" { - entryPath = "/" + entry.Name - } else { - entryPath = entryPath + "/" + entry.Name - } - - // Check for origin mapping - if baseIno, ok := ofs.getOriginIno(entry.Stats.Ino); ok { - entry.Stats.Ino = ofs.getOrCreateOverlayIno(LayerBase, baseIno, entryPath) - } else { - entry.Stats.Ino = ofs.getOrCreateOverlayIno(LayerDelta, entry.Stats.Ino, entryPath) - } - - entriesMap[entry.Name] = entry - } - } - } - - result := make([]DirEntry, 0, len(entriesMap)) - for _, entry := range entriesMap { - result = append(result, entry) - } - return result, nil -} - -// WriteFile writes data to a file, creating it if it doesn't exist. -// Creates in the delta layer and removes any whiteout. -func (ofs *OverlayFS) WriteFile(ctx context.Context, p string, data []byte, mode int64) error { - p = normalizePath(p) - - // Remove whiteout if exists (this also invalidates cache) - if err := ofs.removeWhiteout(ctx, p); err != nil { - return err - } - - // Ensure parent directories exist - parentP := parentPath(p) - if parentP != "/" { - // Check if parent exists in overlay - parentStats, err := ofs.LookupPath(ctx, parentP) - if err != nil { - // Parent doesn't exist, create it - if err := ofs.MkdirAll(ctx, parentP, 0o755); err != nil { - return err - } - } else if !parentStats.IsDir() { - return ErrNotDir("write", parentP) - } - } - - // Invalidate cache before write (inode may change) - ofs.invalidateCache(p) - - // Write to delta layer - return ofs.delta.WriteFile(ctx, p, data, mode) -} - -// ReadFile reads the entire contents of a file. -func (ofs *OverlayFS) ReadFile(ctx context.Context, p string) ([]byte, error) { - p = normalizePath(p) - - // Check for whiteout - if ofs.isWhiteout(p) { - return nil, ErrNoent("read", p) - } - - // Try delta first - data, err := ofs.delta.ReadFile(ctx, p) - if err == nil { - return data, nil - } - if !IsNotExist(err) { - return nil, err - } - - // Try base - need to resolve path to inode - ino, err := ofs.resolvePathToBaseIno(ctx, p) - if err != nil { - return nil, err - } - - return ofs.base.ReadFile(ctx, ino) -} - -// resolvePathToBaseIno resolves a path to a base layer inode. -func (ofs *OverlayFS) resolvePathToBaseIno(ctx context.Context, p string) (int64, error) { - components := strings.Split(strings.Trim(p, "/"), "/") - ino := int64(RootIno) - - for _, comp := range components { - if comp == "" { - continue - } - stats, err := ofs.base.Lookup(ctx, ino, comp) - if err != nil || stats == nil { - return 0, ErrNoent("resolve", p) - } - ino = stats.Ino - } - - return ino, nil -} - -// LookupPath looks up a path and returns its stats. -func (ofs *OverlayFS) LookupPath(ctx context.Context, p string) (*Stats, error) { - p = normalizePath(p) - - if p == "/" { - return ofs.Stat(ctx, RootIno) - } - - // Check for whiteout - if ofs.isWhiteout(p) { - return nil, ErrNoent("lookup", p) - } - - // Check LRU cache first - if ofs.cache != nil { - if ino, ok := ofs.cache.Get(p); ok { - return ofs.Stat(ctx, ino) - } - } - - // Cache miss - do full path resolution - components := strings.Split(strings.Trim(p, "/"), "/") - currentIno := int64(RootIno) - - for _, comp := range components { - stats, err := ofs.Lookup(ctx, currentIno, comp) - if err != nil { - return nil, err - } - currentIno = stats.Ino - } - - // Cache the result - if ofs.cache != nil { - ofs.cache.Set(p, currentIno) - } - - return ofs.Stat(ctx, currentIno) -} - -// Mkdir creates a directory in the delta layer. -func (ofs *OverlayFS) Mkdir(ctx context.Context, p string, mode int64) error { - p = normalizePath(p) - - // Check if whiteout exists - if so, we're recreating a deleted item - wasWhiteout := ofs.isWhiteout(p) - - // Remove whiteout if exists (also invalidates cache) - if err := ofs.removeWhiteout(ctx, p); err != nil { - return err - } - - // Check if already exists in delta layer specifically - // (If it was a whiteout, we know base has it but we want to create new in delta) - if !wasWhiteout { - // Only check overlay if it wasn't a whiteout - if _, err := ofs.LookupPath(ctx, p); err == nil { - return ErrExist("mkdir", p) - } - } else { - // It was a whiteout, check only delta layer - _, err := ofs.delta.Stat(ctx, p) - if err == nil { - return ErrExist("mkdir", p) - } - } - - // Invalidate cache before creating directory - ofs.invalidateCache(p) - - return ofs.delta.Mkdir(ctx, p, mode) -} - -// MkdirAll creates a directory and all parent directories. -func (ofs *OverlayFS) MkdirAll(ctx context.Context, p string, mode int64) error { - p = normalizePath(p) - if p == "/" { - return nil - } - - components := strings.Split(strings.Trim(p, "/"), "/") - currentPath := "" - - for _, component := range components { - currentPath = currentPath + "/" + component - - // Remove whiteout if exists (also invalidates cache) - if err := ofs.removeWhiteout(ctx, currentPath); err != nil { - return err - } - - stats, err := ofs.LookupPath(ctx, currentPath) - if err != nil { - if !IsNotExist(err) { - return err - } - // Invalidate cache before creating - ofs.invalidateCache(currentPath) - // Create in delta - if err := ofs.delta.Mkdir(ctx, currentPath, mode); err != nil { - return err - } - } else if !stats.IsDir() { - return ErrNotDir("mkdir", currentPath) - } - } - - return nil -} - -// Unlink removes a file. -func (ofs *OverlayFS) Unlink(ctx context.Context, p string) error { - p = normalizePath(p) - if p == "/" { - return ErrPerm("unlink", p) - } - - // Check if exists - stats, err := ofs.LookupPath(ctx, p) - if err != nil { - return err - } - if stats.IsDir() { - return ErrIsDir("unlink", p) - } - - // Invalidate cache - ofs.invalidateCache(p) - - // Try to remove from delta - deltaErr := ofs.delta.Unlink(ctx, p) - if deltaErr != nil && !IsNotExist(deltaErr) { - return deltaErr - } - - // Check if exists in base - baseIno, err := ofs.resolvePathToBaseIno(ctx, p) - if err == nil && baseIno != 0 { - // Create whiteout (also invalidates cache) - if err := ofs.createWhiteout(ctx, p); err != nil { - return err - } - } - - return nil -} - -// Rmdir removes an empty directory. -func (ofs *OverlayFS) Rmdir(ctx context.Context, p string) error { - p = normalizePath(p) - if p == "/" { - return ErrPerm("rmdir", p) - } - - // Check if exists and is a directory - stats, err := ofs.LookupPath(ctx, p) - if err != nil { - return err - } - if !stats.IsDir() { - return ErrNotDir("rmdir", p) - } - - // Check if directory is empty (in overlay view) - entries, err := ofs.Readdir(ctx, stats.Ino) - if err != nil { - return err - } - if len(entries) > 0 { - return ErrNotEmpty("rmdir", p) - } - - // Invalidate cache (directory and any cached children) - ofs.invalidateCachePrefix(p) - - // Try to remove from delta - deltaErr := ofs.delta.Rmdir(ctx, p) - if deltaErr != nil && !IsNotExist(deltaErr) { - return deltaErr - } - - // Check if exists in base - baseIno, err := ofs.resolvePathToBaseIno(ctx, p) - if err == nil && baseIno != 0 { - // Create whiteout (also invalidates cache) - if err := ofs.createWhiteout(ctx, p); err != nil { - return err - } - } - - return nil -} - -// Rename moves or renames a file or directory. -func (ofs *OverlayFS) Rename(ctx context.Context, oldPath, newPath string) error { - oldPath = normalizePath(oldPath) - newPath = normalizePath(newPath) - - if oldPath == "/" || newPath == "/" { - return ErrPerm("rename", oldPath) - } - - // Get old stats (to check if it exists and what type) - oldStats, err := ofs.LookupPath(ctx, oldPath) - if err != nil { - return err - } - - // Remove whiteout at destination if exists (also invalidates cache) - if err := ofs.removeWhiteout(ctx, newPath); err != nil { - return err - } - - // Ensure the file is in delta (copy-up if needed) - info, ok := ofs.getInodeInfo(oldStats.Ino) - if !ok { - return ErrNoent("rename", oldPath) - } - - if info.Layer == LayerBase { - // Need to copy up first - _, err := ofs.copyUp(ctx, oldPath, info.UnderlyingIno) - if err != nil { - return err - } - } - - // Invalidate cache for both old and new paths (and children if directory) - ofs.invalidateCachePrefix(oldPath) - ofs.invalidateCachePrefix(newPath) - - // Perform rename in delta - if err := ofs.delta.Rename(ctx, oldPath, newPath); err != nil { - return err - } - - // Create whiteout at old path if it existed in base (also invalidates cache) - baseIno, err := ofs.resolvePathToBaseIno(ctx, oldPath) - if err == nil && baseIno != 0 { - if err := ofs.createWhiteout(ctx, oldPath); err != nil { - return err - } - } - - return nil -} - -// Link creates a hard link. -func (ofs *OverlayFS) Link(ctx context.Context, existingPath, newPath string) error { - existingPath = normalizePath(existingPath) - newPath = normalizePath(newPath) - - // Get existing stats - stats, err := ofs.LookupPath(ctx, existingPath) - if err != nil { - return err - } - if stats.IsDir() { - return ErrPerm("link", existingPath) - } - - // Ensure the file is in delta - info, ok := ofs.getInodeInfo(stats.Ino) - if !ok { - return ErrNoent("link", existingPath) - } - - if info.Layer == LayerBase { - // Copy up first - _, err := ofs.copyUp(ctx, existingPath, info.UnderlyingIno) - if err != nil { - return err - } - } - - // Remove whiteout at destination if exists (also invalidates cache) - if err := ofs.removeWhiteout(ctx, newPath); err != nil { - return err - } - - // Invalidate cache for new path - ofs.invalidateCache(newPath) - - return ofs.delta.Link(ctx, existingPath, newPath) -} - -// Symlink creates a symbolic link. -func (ofs *OverlayFS) Symlink(ctx context.Context, target, linkPath string) error { - linkPath = normalizePath(linkPath) - - // Remove whiteout if exists (also invalidates cache) - if err := ofs.removeWhiteout(ctx, linkPath); err != nil { - return err - } - - // Invalidate cache for link path - ofs.invalidateCache(linkPath) - - return ofs.delta.Symlink(ctx, target, linkPath) -} - -// Readlink returns the target of a symbolic link. -func (ofs *OverlayFS) Readlink(ctx context.Context, p string) (string, error) { - p = normalizePath(p) - - // Check for whiteout - if ofs.isWhiteout(p) { - return "", ErrNoent("readlink", p) - } - - // Try delta first - target, err := ofs.delta.Readlink(ctx, p) - if err == nil { - return target, nil - } - if !IsNotExist(err) { - return "", err - } - - // Try base - ino, err := ofs.resolvePathToBaseIno(ctx, p) - if err != nil { - return "", err - } - - return ofs.base.Readlink(ctx, ino) -} - -// Chmod changes file permissions. -func (ofs *OverlayFS) Chmod(ctx context.Context, p string, mode int64) error { - p = normalizePath(p) - - stats, err := ofs.LookupPath(ctx, p) - if err != nil { - return err - } - - info, ok := ofs.getInodeInfo(stats.Ino) - if !ok { - return ErrNoent("chmod", p) - } - - if info.Layer == LayerBase { - // Copy up first - _, err := ofs.copyUp(ctx, p, info.UnderlyingIno) - if err != nil { - return err - } - } - - return ofs.delta.Chmod(ctx, p, mode) -} - -// Utimes updates file timestamps. -func (ofs *OverlayFS) Utimes(ctx context.Context, p string, atime, mtime int64) error { - p = normalizePath(p) - - stats, err := ofs.LookupPath(ctx, p) - if err != nil { - return err - } - - info, ok := ofs.getInodeInfo(stats.Ino) - if !ok { - return ErrNoent("utimes", p) - } - - if info.Layer == LayerBase { - // Copy up first - _, err := ofs.copyUp(ctx, p, info.UnderlyingIno) - if err != nil { - return err - } - } - - return ofs.delta.Utimes(ctx, p, atime, mtime) -} - -// Utimens updates file timestamps with selective control. -func (ofs *OverlayFS) Utimens(ctx context.Context, p string, atime, mtime TimeChange) error { - p = normalizePath(p) - - stats, err := ofs.LookupPath(ctx, p) - if err != nil { - return err - } - - info, ok := ofs.getInodeInfo(stats.Ino) - if !ok { - return ErrNoent("utimens", p) - } - - if info.Layer == LayerBase { - _, err := ofs.copyUp(ctx, p, info.UnderlyingIno) - if err != nil { - return err - } - } - - return ofs.delta.Utimens(ctx, p, atime, mtime) -} - -// ============================================================================= -// Cache Operations -// ============================================================================= - -// invalidateCache invalidates the cache entry for a single path. -func (ofs *OverlayFS) invalidateCache(p string) { - if ofs.cache != nil { - ofs.cache.Delete(p) - } -} - -// invalidateCachePrefix invalidates all cache entries with the given prefix. -// Used when a directory is deleted or renamed. -func (ofs *OverlayFS) invalidateCachePrefix(prefix string) { - if ofs.cache != nil { - ofs.cache.Delete(prefix) - ofs.cache.DeletePrefix(prefix + "/") - } -} - -// CacheStats returns cache statistics, or nil if caching is disabled. -func (ofs *OverlayFS) CacheStats() *cache.Stats { - if ofs.cache == nil { - return nil - } - stats := ofs.cache.Stats() - return &stats -} - -// ClearCache clears all cached path resolutions. -// This is useful when external changes may have occurred. -func (ofs *OverlayFS) ClearCache() { - if ofs.cache != nil { - ofs.cache.Clear() - } -} diff --git a/sdk/go/overlay_test.go b/sdk/go/overlay_test.go deleted file mode 100644 index bb01b1fd..00000000 --- a/sdk/go/overlay_test.go +++ /dev/null @@ -1,1643 +0,0 @@ -package agentfs - -import ( - "context" - "testing" -) - -// MockBaseFS is a mock implementation of BaseFS for testing. -type MockBaseFS struct { - files map[int64]*mockFile - dirs map[int64]*mockDir -} - -type mockFile struct { - stats Stats - content []byte - target string // for symlinks -} - -type mockDir struct { - stats Stats - entries map[string]int64 // name -> ino -} - -func newMockBaseFS() *MockBaseFS { - fs := &MockBaseFS{ - files: make(map[int64]*mockFile), - dirs: make(map[int64]*mockDir), - } - - // Create root directory - fs.dirs[1] = &mockDir{ - stats: Stats{ - Ino: 1, - Mode: S_IFDIR | 0o755, - Nlink: 2, - }, - entries: make(map[string]int64), - } - - return fs -} - -func (fs *MockBaseFS) addFile(parentIno int64, name string, ino int64, content []byte, mode int64) { - fs.files[ino] = &mockFile{ - stats: Stats{ - Ino: ino, - Mode: S_IFREG | mode, - Nlink: 1, - Size: int64(len(content)), - }, - content: content, - } - if dir, ok := fs.dirs[parentIno]; ok { - dir.entries[name] = ino - } -} - -func (fs *MockBaseFS) addDir(parentIno int64, name string, ino int64, mode int64) { - fs.dirs[ino] = &mockDir{ - stats: Stats{ - Ino: ino, - Mode: S_IFDIR | mode, - Nlink: 2, - }, - entries: make(map[string]int64), - } - if dir, ok := fs.dirs[parentIno]; ok { - dir.entries[name] = ino - } -} - -func (fs *MockBaseFS) addSymlink(parentIno int64, name string, ino int64, target string) { - fs.files[ino] = &mockFile{ - stats: Stats{ - Ino: ino, - Mode: S_IFLNK | 0o777, - Nlink: 1, - Size: int64(len(target)), - }, - target: target, - } - if dir, ok := fs.dirs[parentIno]; ok { - dir.entries[name] = ino - } -} - -func (fs *MockBaseFS) Stat(ctx context.Context, ino int64) (*Stats, error) { - if f, ok := fs.files[ino]; ok { - stats := f.stats - return &stats, nil - } - if d, ok := fs.dirs[ino]; ok { - stats := d.stats - return &stats, nil - } - return nil, ErrNoent("stat", "") -} - -func (fs *MockBaseFS) Lookup(ctx context.Context, parentIno int64, name string) (*Stats, error) { - dir, ok := fs.dirs[parentIno] - if !ok { - return nil, ErrNoent("lookup", "") - } - - childIno, ok := dir.entries[name] - if !ok { - return nil, ErrNoent("lookup", name) - } - - return fs.Stat(ctx, childIno) -} - -func (fs *MockBaseFS) Readdir(ctx context.Context, ino int64) ([]string, error) { - dir, ok := fs.dirs[ino] - if !ok { - return nil, ErrNoent("readdir", "") - } - - names := make([]string, 0, len(dir.entries)) - for name := range dir.entries { - names = append(names, name) - } - return names, nil -} - -func (fs *MockBaseFS) ReaddirPlus(ctx context.Context, ino int64) ([]DirEntry, error) { - dir, ok := fs.dirs[ino] - if !ok { - return nil, ErrNoent("readdirplus", "") - } - - entries := make([]DirEntry, 0, len(dir.entries)) - for name, childIno := range dir.entries { - stats, err := fs.Stat(ctx, childIno) - if err != nil { - continue - } - entries = append(entries, DirEntry{Name: name, Stats: stats}) - } - return entries, nil -} - -func (fs *MockBaseFS) ReadFile(ctx context.Context, ino int64) ([]byte, error) { - f, ok := fs.files[ino] - if !ok { - return nil, ErrNoent("read", "") - } - if f.stats.IsSymlink() { - return nil, ErrInval("read", "", "is a symlink") - } - return f.content, nil -} - -func (fs *MockBaseFS) Readlink(ctx context.Context, ino int64) (string, error) { - f, ok := fs.files[ino] - if !ok { - return "", ErrNoent("readlink", "") - } - if !f.stats.IsSymlink() { - return "", ErrInval("readlink", "", "not a symlink") - } - return f.target, nil -} - -// setupOverlayTest creates a test overlay filesystem -func setupOverlayTest(t *testing.T) (*OverlayFS, *MockBaseFS, *AgentFS) { - ctx := context.Background() - afs := setupTestDB(t) - - base := newMockBaseFS() - - ofs := NewOverlayFS(base, afs.FS, afs.db) - if err := ofs.Init(ctx); err != nil { - t.Fatalf("OverlayFS.Init failed: %v", err) - } - - return ofs, base, afs -} - -// ============================================================================= -// SPEC Consistency Rule Tests -// ============================================================================= - -// TestOverlay_ConsistencyRule1_WhiteoutRemovedOnCreate tests: -// "A whiteout MUST be removed when a new file is created at that path" -func TestOverlay_ConsistencyRule1_WhiteoutRemovedOnCreate(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add a file to base - base.addFile(1, "test.txt", 100, []byte("base content"), 0o644) - - // Verify file is visible - _, err := ofs.LookupPath(ctx, "/test.txt") - if err != nil { - t.Fatalf("LookupPath failed: %v", err) - } - - // Delete the file (creates whiteout) - err = ofs.Unlink(ctx, "/test.txt") - if err != nil { - t.Fatalf("Unlink failed: %v", err) - } - - // Verify whiteout exists - if !ofs.isWhiteout("/test.txt") { - t.Error("Expected whiteout to exist after Unlink") - } - - // File should not be visible - _, err = ofs.LookupPath(ctx, "/test.txt") - if !IsNotExist(err) { - t.Errorf("Expected ENOENT after whiteout, got: %v", err) - } - - // Create a new file at the same path - err = ofs.WriteFile(ctx, "/test.txt", []byte("new content"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Verify whiteout is removed - if ofs.isWhiteout("/test.txt") { - t.Error("Expected whiteout to be removed after WriteFile") - } - - // File should be visible with new content - data, err := ofs.ReadFile(ctx, "/test.txt") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - if string(data) != "new content" { - t.Errorf("Expected 'new content', got %q", data) - } -} - -// TestOverlay_ConsistencyRule2_WhiteoutCreatedOnDelete tests: -// "A whiteout MUST be created when deleting a file that exists in the base layer" -func TestOverlay_ConsistencyRule2_WhiteoutCreatedOnDelete(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add files to base - base.addFile(1, "base_file.txt", 100, []byte("base content"), 0o644) - - // Add file only in delta - err := ofs.WriteFile(ctx, "/delta_file.txt", []byte("delta content"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Delete base file - should create whiteout - err = ofs.Unlink(ctx, "/base_file.txt") - if err != nil { - t.Fatalf("Unlink base file failed: %v", err) - } - - if !ofs.isWhiteout("/base_file.txt") { - t.Error("Expected whiteout for base file after delete") - } - - // Delete delta file - should NOT create whiteout - err = ofs.Unlink(ctx, "/delta_file.txt") - if err != nil { - t.Fatalf("Unlink delta file failed: %v", err) - } - - if ofs.isWhiteout("/delta_file.txt") { - t.Error("Should NOT create whiteout for delta-only file") - } -} - -// TestOverlay_ConsistencyRule3_ParentPathCorrectlyDerived tests: -// "The parent_path MUST be correctly derived from path" -func TestOverlay_ConsistencyRule3_ParentPathCorrectlyDerived(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add nested structure to base - base.addDir(1, "dir", 100, 0o755) - base.addFile(100, "file.txt", 101, []byte("content"), 0o644) - - // Delete the nested file - err := ofs.Unlink(ctx, "/dir/file.txt") - if err != nil { - t.Fatalf("Unlink failed: %v", err) - } - - // Verify whiteout was created with correct parent_path - // The parent_path should be "/dir" for path "/dir/file.txt" - var storedParent string - err = afs.db.QueryRowContext(ctx, "SELECT parent_path FROM fs_whiteout WHERE path = ?", "/dir/file.txt").Scan(&storedParent) - if err != nil { - t.Fatalf("Query whiteout failed: %v", err) - } - - if storedParent != "/dir" { - t.Errorf("Expected parent_path '/dir', got %q", storedParent) - } - - // Test root-level file - base.addFile(1, "root_file.txt", 102, []byte("root"), 0o644) - err = ofs.Unlink(ctx, "/root_file.txt") - if err != nil { - t.Fatalf("Unlink root file failed: %v", err) - } - - err = afs.db.QueryRowContext(ctx, "SELECT parent_path FROM fs_whiteout WHERE path = ?", "/root_file.txt").Scan(&storedParent) - if err != nil { - t.Fatalf("Query whiteout failed: %v", err) - } - - if storedParent != "/" { - t.Errorf("Expected parent_path '/', got %q", storedParent) - } -} - -// TestOverlay_ConsistencyRule4_WhiteoutsAffectOverlayLookups tests: -// "Whiteouts only affect overlay lookups, not the underlying base filesystem" -func TestOverlay_ConsistencyRule4_WhiteoutsAffectOverlayLookups(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add file to base - base.addFile(1, "test.txt", 100, []byte("base content"), 0o644) - - // Verify file exists in base directly - _, err := base.Lookup(ctx, 1, "test.txt") - if err != nil { - t.Fatalf("Base lookup should succeed: %v", err) - } - - // Delete through overlay (creates whiteout) - err = ofs.Unlink(ctx, "/test.txt") - if err != nil { - t.Fatalf("Unlink failed: %v", err) - } - - // Overlay lookup should fail - _, err = ofs.LookupPath(ctx, "/test.txt") - if !IsNotExist(err) { - t.Errorf("Overlay lookup should return ENOENT, got: %v", err) - } - - // Base lookup should still succeed (whiteout doesn't affect base) - baseStats, err := base.Lookup(ctx, 1, "test.txt") - if err != nil { - t.Errorf("Base lookup should still succeed after overlay delete: %v", err) - } - if baseStats == nil { - t.Error("Base lookup returned nil stats") - } -} - -// TestOverlay_ConsistencyRule5_OriginMappingStored tests: -// "When copying a file from base to delta, the origin mapping MUST be stored" -func TestOverlay_ConsistencyRule5_OriginMappingStored(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add file to base - baseIno := int64(100) - base.addFile(1, "test.txt", baseIno, []byte("base content"), 0o644) - - // Modify the file through overlay (triggers copy-up) - err := ofs.Chmod(ctx, "/test.txt", 0o600) - if err != nil { - t.Fatalf("Chmod failed: %v", err) - } - - // Verify origin mapping was stored in database - var storedBaseIno int64 - rows, err := afs.db.QueryContext(ctx, "SELECT delta_ino, base_ino FROM fs_origin") - if err != nil { - t.Fatalf("Query origin failed: %v", err) - } - defer rows.Close() - - found := false - var deltaInoWithOrigin int64 - for rows.Next() { - var deltaIno int64 - if err := rows.Scan(&deltaIno, &storedBaseIno); err != nil { - t.Fatalf("Scan failed: %v", err) - } - if storedBaseIno == baseIno { - found = true - deltaInoWithOrigin = deltaIno - break - } - } - - if !found { - t.Errorf("Expected origin mapping for base_ino %d to be stored", baseIno) - } - - // Verify origin mapping is in memory - originBaseIno, ok := ofs.getOriginIno(deltaInoWithOrigin) - if !ok { - t.Error("Origin mapping not found in memory") - } - if originBaseIno != baseIno { - t.Errorf("Origin mapping in memory: expected base_ino %d, got %d", baseIno, originBaseIno) - } - - // Verify the file now exists in delta layer - deltaStats, err := ofs.delta.Stat(ctx, "/test.txt") - if err != nil { - t.Fatalf("Delta stat failed: %v", err) - } - if deltaStats == nil { - t.Fatal("File should exist in delta after copy-up") - } -} - -// TestOverlay_ConsistencyRule6_BaseInodeReturnedForCopiedUp tests: -// "When stat'ing a delta file with an origin mapping, the base inode MUST be returned" -func TestOverlay_ConsistencyRule6_BaseInodeReturnedForCopiedUp(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add file to base - baseIno := int64(100) - base.addFile(1, "test.txt", baseIno, []byte("base content"), 0o644) - - // Get initial overlay inode - statsBefore, err := ofs.LookupPath(ctx, "/test.txt") - if err != nil { - t.Fatalf("LookupPath before copy-up failed: %v", err) - } - inoBefore := statsBefore.Ino - - // Modify the file (triggers copy-up) - err = ofs.Chmod(ctx, "/test.txt", 0o600) - if err != nil { - t.Fatalf("Chmod failed: %v", err) - } - - // Get overlay inode after copy-up - statsAfter, err := ofs.LookupPath(ctx, "/test.txt") - if err != nil { - t.Fatalf("LookupPath after copy-up failed: %v", err) - } - inoAfter := statsAfter.Ino - - // The overlay inode should remain stable (same before and after copy-up) - // This ensures kernel inode caches don't get confused - if inoBefore != inoAfter { - t.Errorf("Overlay inode changed after copy-up: %d -> %d", inoBefore, inoAfter) - } - - // Verify the file was actually modified - if statsAfter.Mode&0o777 != 0o600 { - t.Errorf("Expected mode 0600, got %o", statsAfter.Mode&0o777) - } -} - -// ============================================================================= -// Additional Overlay Tests -// ============================================================================= - -func TestOverlay_BasicReadWrite(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add file to base - base.addFile(1, "base.txt", 100, []byte("base content"), 0o644) - - // Read from base through overlay - data, err := ofs.ReadFile(ctx, "/base.txt") - if err != nil { - t.Fatalf("ReadFile base failed: %v", err) - } - if string(data) != "base content" { - t.Errorf("Expected 'base content', got %q", data) - } - - // Write to delta - err = ofs.WriteFile(ctx, "/delta.txt", []byte("delta content"), 0o644) - if err != nil { - t.Fatalf("WriteFile delta failed: %v", err) - } - - // Read from delta - data, err = ofs.ReadFile(ctx, "/delta.txt") - if err != nil { - t.Fatalf("ReadFile delta failed: %v", err) - } - if string(data) != "delta content" { - t.Errorf("Expected 'delta content', got %q", data) - } -} - -func TestOverlay_DeltaOverridesBase(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add file to base - base.addFile(1, "test.txt", 100, []byte("base content"), 0o644) - - // Overwrite in delta - err := ofs.WriteFile(ctx, "/test.txt", []byte("delta content"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Read should return delta content - data, err := ofs.ReadFile(ctx, "/test.txt") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - if string(data) != "delta content" { - t.Errorf("Expected 'delta content', got %q", data) - } -} - -func TestOverlay_ReaddirMergesLayers(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add files to base - base.addFile(1, "base1.txt", 100, []byte("base1"), 0o644) - base.addFile(1, "base2.txt", 101, []byte("base2"), 0o644) - - // Add files to delta - err := ofs.WriteFile(ctx, "/delta1.txt", []byte("delta1"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Readdir should merge both layers - entries, err := ofs.Readdir(ctx, 1) - if err != nil { - t.Fatalf("Readdir failed: %v", err) - } - - expected := map[string]bool{ - "base1.txt": true, - "base2.txt": true, - "delta1.txt": true, - } - - for _, name := range entries { - if !expected[name] { - t.Errorf("Unexpected entry: %s", name) - } - delete(expected, name) - } - - for name := range expected { - t.Errorf("Missing entry: %s", name) - } -} - -func TestOverlay_ReaddirExcludesWhiteouts(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add files to base - base.addFile(1, "keep.txt", 100, []byte("keep"), 0o644) - base.addFile(1, "delete.txt", 101, []byte("delete"), 0o644) - - // Delete one file - err := ofs.Unlink(ctx, "/delete.txt") - if err != nil { - t.Fatalf("Unlink failed: %v", err) - } - - // Readdir should exclude the deleted file - entries, err := ofs.Readdir(ctx, 1) - if err != nil { - t.Fatalf("Readdir failed: %v", err) - } - - for _, name := range entries { - if name == "delete.txt" { - t.Error("Deleted file should not appear in readdir") - } - } -} - -func TestOverlay_RmdirCreatesWhiteout(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add empty directory to base - base.addDir(1, "emptydir", 100, 0o755) - - // Remove directory - err := ofs.Rmdir(ctx, "/emptydir") - if err != nil { - t.Fatalf("Rmdir failed: %v", err) - } - - // Verify whiteout exists - if !ofs.isWhiteout("/emptydir") { - t.Error("Expected whiteout after Rmdir") - } - - // Directory should not be visible - _, err = ofs.LookupPath(ctx, "/emptydir") - if !IsNotExist(err) { - t.Errorf("Expected ENOENT, got: %v", err) - } -} - -func TestOverlay_MkdirRemovesWhiteout(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add directory to base - base.addDir(1, "testdir", 100, 0o755) - - // Delete it - err := ofs.Rmdir(ctx, "/testdir") - if err != nil { - t.Fatalf("Rmdir failed: %v", err) - } - - // Recreate it - err = ofs.Mkdir(ctx, "/testdir", 0o755) - if err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - - // Whiteout should be removed - if ofs.isWhiteout("/testdir") { - t.Error("Whiteout should be removed after Mkdir") - } - - // Directory should be visible - stats, err := ofs.LookupPath(ctx, "/testdir") - if err != nil { - t.Fatalf("LookupPath failed: %v", err) - } - if !stats.IsDir() { - t.Error("Expected directory") - } -} - -func TestOverlay_CopyUpOnModify(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add file to base - base.addFile(1, "test.txt", 100, []byte("original"), 0o644) - - // Modify through chmod (triggers copy-up) - err := ofs.Chmod(ctx, "/test.txt", 0o600) - if err != nil { - t.Fatalf("Chmod failed: %v", err) - } - - // Verify the file is now in delta - stats, err := ofs.delta.Stat(ctx, "/test.txt") - if err != nil { - t.Fatalf("Delta stat failed: %v", err) - } - if stats.Mode&0o777 != 0o600 { - t.Errorf("Expected mode 0600, got %o", stats.Mode&0o777) - } - - // Original base file should be unchanged - baseStats, err := base.Lookup(ctx, 1, "test.txt") - if err != nil { - t.Fatalf("Base lookup failed: %v", err) - } - if baseStats.Mode&0o777 != 0o644 { - t.Errorf("Base file mode should be unchanged: %o", baseStats.Mode&0o777) - } -} - -func TestOverlay_SymlinkCopyUp(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add symlink to base - base.addSymlink(1, "link", 100, "/target") - - // Read symlink through overlay - target, err := ofs.Readlink(ctx, "/link") - if err != nil { - t.Fatalf("Readlink failed: %v", err) - } - if target != "/target" { - t.Errorf("Expected '/target', got %q", target) - } -} - -func TestOverlay_WhiteoutAncestorBlocks(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add nested structure to base - base.addDir(1, "parent", 100, 0o755) - base.addFile(100, "child.txt", 101, []byte("child"), 0o644) - - // Delete parent directory (after ensuring it's "empty" in overlay view) - // First, delete the child - err := ofs.Unlink(ctx, "/parent/child.txt") - if err != nil { - t.Fatalf("Unlink child failed: %v", err) - } - - // Now delete the parent - err = ofs.Rmdir(ctx, "/parent") - if err != nil { - t.Fatalf("Rmdir parent failed: %v", err) - } - - // Parent is whiteout, so child should also be blocked - // (isWhiteout checks ancestors) - if !ofs.isWhiteout("/parent") { - t.Error("Expected /parent to be whiteout") - } - if !ofs.isWhiteout("/parent/child.txt") { - t.Error("Expected /parent/child.txt to be blocked by ancestor whiteout") - } -} - -// ============================================================================= -// Cache Tests -// ============================================================================= - -func setupOverlayTestWithCache(t *testing.T) (*OverlayFS, *MockBaseFS, *AgentFS) { - t.Helper() - ctx := context.Background() - - // Create AgentFS - afs, err := Open(ctx, AgentFSOptions{Path: ":memory:"}) - if err != nil { - t.Fatalf("Failed to open AgentFS: %v", err) - } - - // Create mock base filesystem - base := newMockBaseFS() - - // Create overlay filesystem with cache enabled - ofs := NewOverlayFSWithCache(base, afs.FS, afs.db, OverlayCacheOptions{ - Enabled: true, - MaxEntries: 1000, - }) - if err := ofs.Init(ctx); err != nil { - _ = afs.Close() - t.Fatalf("Failed to init OverlayFS: %v", err) - } - - return ofs, base, afs -} - -func TestOverlayCache_BasicCaching(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTestWithCache(t) - defer afs.Close() - - // Add file to base - base.addFile(1, "test.txt", 100, []byte("hello"), 0o644) - - // First lookup should miss cache - _, err := ofs.LookupPath(ctx, "/test.txt") - if err != nil { - t.Fatalf("LookupPath failed: %v", err) - } - - stats := ofs.CacheStats() - if stats == nil { - t.Fatal("CacheStats should not be nil when cache is enabled") - } - initialMisses := stats.Misses - - // Second lookup should hit cache - _, err = ofs.LookupPath(ctx, "/test.txt") - if err != nil { - t.Fatalf("LookupPath failed: %v", err) - } - - stats = ofs.CacheStats() - if stats.Hits == 0 { - t.Error("Expected cache hit on second lookup") - } - if stats.Misses != initialMisses { - t.Error("Expected no additional misses on second lookup") - } -} - -func TestOverlayCache_InvalidationOnUnlink(t *testing.T) { - ctx := context.Background() - ofs, _, afs := setupOverlayTestWithCache(t) - defer afs.Close() - - // Create a file in delta - err := ofs.WriteFile(ctx, "/to_delete.txt", []byte("x"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Lookup to populate cache - _, _ = ofs.LookupPath(ctx, "/to_delete.txt") - - stats := ofs.CacheStats() - entriesBefore := stats.Entries - - // Delete the file - err = ofs.Unlink(ctx, "/to_delete.txt") - if err != nil { - t.Fatalf("Unlink failed: %v", err) - } - - // Cache entry should be invalidated - stats = ofs.CacheStats() - if stats.Entries >= entriesBefore { - t.Error("Cache entry should be invalidated after Unlink") - } - - // Lookup should fail - _, err = ofs.LookupPath(ctx, "/to_delete.txt") - if !IsNotExist(err) { - t.Error("Expected ENOENT after Unlink") - } -} - -func TestOverlayCache_InvalidationOnRename(t *testing.T) { - ctx := context.Background() - ofs, _, afs := setupOverlayTestWithCache(t) - defer afs.Close() - - // Create a file in delta - err := ofs.WriteFile(ctx, "/old_name.txt", []byte("x"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Populate cache - _, _ = ofs.LookupPath(ctx, "/old_name.txt") - - // Rename - err = ofs.Rename(ctx, "/old_name.txt", "/new_name.txt") - if err != nil { - t.Fatalf("Rename failed: %v", err) - } - - // Old path should not exist - _, err = ofs.LookupPath(ctx, "/old_name.txt") - if !IsNotExist(err) { - t.Error("Expected ENOENT for old path after rename") - } - - // New path should work - _, err = ofs.LookupPath(ctx, "/new_name.txt") - if err != nil { - t.Errorf("New path should exist: %v", err) - } -} - -func TestOverlayCache_InvalidationOnWhiteout(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTestWithCache(t) - defer afs.Close() - - // Add file to base - base.addFile(1, "base_file.txt", 100, []byte("hello"), 0o644) - - // Lookup to populate cache - _, err := ofs.LookupPath(ctx, "/base_file.txt") - if err != nil { - t.Fatalf("LookupPath failed: %v", err) - } - - stats := ofs.CacheStats() - if stats.Entries == 0 { - t.Fatal("Expected cache entry after lookup") - } - - // Delete file (creates whiteout) - err = ofs.Unlink(ctx, "/base_file.txt") - if err != nil { - t.Fatalf("Unlink failed: %v", err) - } - - // Lookup should fail (whiteout hides base file) - _, err = ofs.LookupPath(ctx, "/base_file.txt") - if !IsNotExist(err) { - t.Error("Expected ENOENT after whiteout created") - } -} - -func TestOverlayCache_ClearCache(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTestWithCache(t) - defer afs.Close() - - // Add files to base and populate cache - for i := int64(0); i < 10; i++ { - name := string(rune('a'+i)) + ".txt" - base.addFile(1, name, 100+i, []byte("x"), 0o644) - _, _ = ofs.LookupPath(ctx, "/"+name) - } - - stats := ofs.CacheStats() - if stats.Entries == 0 { - t.Fatal("Cache should have entries") - } - - // Clear cache - ofs.ClearCache() - - stats = ofs.CacheStats() - if stats.Entries != 0 { - t.Error("Cache should be empty after ClearCache") - } -} - -func TestOverlayCache_Disabled(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) // Uses non-cached setup - defer afs.Close() - - // CacheStats should return nil when cache is disabled - stats := ofs.CacheStats() - if stats != nil { - t.Error("CacheStats should be nil when cache is disabled") - } - - // Operations should still work - base.addFile(1, "test.txt", 100, []byte("hello"), 0o644) - _, err := ofs.LookupPath(ctx, "/test.txt") - if err != nil { - t.Fatalf("LookupPath failed: %v", err) - } -} - -func TestOverlayCache_InvalidationOnCopyUp(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTestWithCache(t) - defer afs.Close() - - // Add file to base - base.addFile(1, "to_modify.txt", 100, []byte("original"), 0o644) - - // Lookup to populate cache - stats1, err := ofs.LookupPath(ctx, "/to_modify.txt") - if err != nil { - t.Fatalf("LookupPath failed: %v", err) - } - ino1 := stats1.Ino - - // Modify file (triggers copy-up) - err = ofs.WriteFile(ctx, "/to_modify.txt", []byte("modified"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Lookup after copy-up should return correct data - stats2, err := ofs.LookupPath(ctx, "/to_modify.txt") - if err != nil { - t.Fatalf("LookupPath after copy-up failed: %v", err) - } - - // Read should return modified content - content, err := ofs.ReadFile(ctx, "/to_modify.txt") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - if string(content) != "modified" { - t.Errorf("Expected 'modified', got %q", string(content)) - } - - // Inode might change after copy-up (implementation detail) - _ = ino1 - _ = stats2 -} - -// ============================================================================= -// Parameterized Cache Consistency Tests -// ============================================================================= -// These tests run the same operations with caching enabled and disabled -// to verify that results are consistent regardless of cache state. - -type cacheTestConfig struct { - name string - cacheEnabled bool -} - -var cacheConfigs = []cacheTestConfig{ - {"WithCache", true}, - {"WithoutCache", false}, -} - -func setupOverlayWithConfig(t *testing.T, cfg cacheTestConfig) (*OverlayFS, *MockBaseFS, *AgentFS) { - t.Helper() - ctx := context.Background() - - afs, err := Open(ctx, AgentFSOptions{Path: ":memory:"}) - if err != nil { - t.Fatalf("Failed to open AgentFS: %v", err) - } - - base := newMockBaseFS() - - var ofs *OverlayFS - if cfg.cacheEnabled { - ofs = NewOverlayFSWithCache(base, afs.FS, afs.db, OverlayCacheOptions{ - Enabled: true, - MaxEntries: 1000, - }) - } else { - ofs = NewOverlayFS(base, afs.FS, afs.db) - } - - if err := ofs.Init(ctx); err != nil { - _ = afs.Close() - t.Fatalf("Failed to init OverlayFS: %v", err) - } - - return ofs, base, afs -} - -// TestOverlayCacheConsistency_FileOperations tests basic file operations -// produce identical results with and without caching. -func TestOverlayCacheConsistency_FileOperations(t *testing.T) { - for _, cfg := range cacheConfigs { - t.Run(cfg.name, func(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayWithConfig(t, cfg) - defer afs.Close() - - // Setup: Add files to base - base.addFile(1, "base_file.txt", 100, []byte("base content"), 0o644) - base.addDir(1, "basedir", 101, 0o755) - base.addFile(101, "nested.txt", 102, []byte("nested content"), 0o644) - - // Test 1: Read base file - content, err := ofs.ReadFile(ctx, "/base_file.txt") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - if string(content) != "base content" { - t.Errorf("Expected 'base content', got %q", string(content)) - } - - // Test 2: Read base file multiple times (exercises cache) - for i := 0; i < 5; i++ { - content, err = ofs.ReadFile(ctx, "/base_file.txt") - if err != nil { - t.Fatalf("ReadFile iteration %d failed: %v", i, err) - } - if string(content) != "base content" { - t.Errorf("Iteration %d: Expected 'base content', got %q", i, string(content)) - } - } - - // Test 3: Write new file - err = ofs.WriteFile(ctx, "/new_file.txt", []byte("new content"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Test 4: Read new file multiple times - for i := 0; i < 5; i++ { - content, err = ofs.ReadFile(ctx, "/new_file.txt") - if err != nil { - t.Fatalf("ReadFile new file iteration %d failed: %v", i, err) - } - if string(content) != "new content" { - t.Errorf("Iteration %d: Expected 'new content', got %q", i, string(content)) - } - } - - // Test 5: Overwrite base file - err = ofs.WriteFile(ctx, "/base_file.txt", []byte("overwritten"), 0o644) - if err != nil { - t.Fatalf("WriteFile overwrite failed: %v", err) - } - content, err = ofs.ReadFile(ctx, "/base_file.txt") - if err != nil { - t.Fatalf("ReadFile after overwrite failed: %v", err) - } - if string(content) != "overwritten" { - t.Errorf("Expected 'overwritten', got %q", string(content)) - } - - // Test 6: Read nested file - content, err = ofs.ReadFile(ctx, "/basedir/nested.txt") - if err != nil { - t.Fatalf("ReadFile nested failed: %v", err) - } - if string(content) != "nested content" { - t.Errorf("Expected 'nested content', got %q", string(content)) - } - }) - } -} - -// TestOverlayCacheConsistency_DirectoryOperations tests directory operations -// produce identical results with and without caching. -func TestOverlayCacheConsistency_DirectoryOperations(t *testing.T) { - for _, cfg := range cacheConfigs { - t.Run(cfg.name, func(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayWithConfig(t, cfg) - defer afs.Close() - - // Setup: Add directory structure to base - base.addDir(1, "basedir", 100, 0o755) - base.addFile(100, "file1.txt", 101, []byte("f1"), 0o644) - base.addFile(100, "file2.txt", 102, []byte("f2"), 0o644) - - // Test 1: Readdir on base directory - entries, err := ofs.Readdir(ctx, 1) - if err != nil { - t.Fatalf("Readdir root failed: %v", err) - } - if !containsEntry(entries, "basedir") { - t.Error("Expected 'basedir' in root entries") - } - - // Test 2: Create new directory - err = ofs.Mkdir(ctx, "/newdir", 0o755) - if err != nil { - t.Fatalf("Mkdir failed: %v", err) - } - - // Test 3: Readdir should show both directories - entries, err = ofs.Readdir(ctx, 1) - if err != nil { - t.Fatalf("Readdir after mkdir failed: %v", err) - } - if !containsEntry(entries, "basedir") || !containsEntry(entries, "newdir") { - t.Errorf("Expected both 'basedir' and 'newdir', got %v", entries) - } - - // Test 4: MkdirAll with nested path - err = ofs.MkdirAll(ctx, "/deep/nested/path", 0o755) - if err != nil { - t.Fatalf("MkdirAll failed: %v", err) - } - - // Test 5: Verify deep path exists - stats, err := ofs.LookupPath(ctx, "/deep/nested/path") - if err != nil { - t.Fatalf("LookupPath deep path failed: %v", err) - } - if !stats.IsDir() { - t.Error("Expected directory") - } - - // Test 6: Write file in deep path - err = ofs.WriteFile(ctx, "/deep/nested/path/file.txt", []byte("deep"), 0o644) - if err != nil { - t.Fatalf("WriteFile in deep path failed: %v", err) - } - content, err := ofs.ReadFile(ctx, "/deep/nested/path/file.txt") - if err != nil { - t.Fatalf("ReadFile from deep path failed: %v", err) - } - if string(content) != "deep" { - t.Errorf("Expected 'deep', got %q", string(content)) - } - }) - } -} - -// TestOverlayCacheConsistency_DeleteOperations tests delete operations -// produce identical results with and without caching. -func TestOverlayCacheConsistency_DeleteOperations(t *testing.T) { - for _, cfg := range cacheConfigs { - t.Run(cfg.name, func(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayWithConfig(t, cfg) - defer afs.Close() - - // Setup: Add files to base and delta - base.addFile(1, "base_to_delete.txt", 100, []byte("base"), 0o644) - base.addDir(1, "base_dir", 101, 0o755) - base.addFile(101, "in_dir.txt", 102, []byte("in dir"), 0o644) - - err := ofs.WriteFile(ctx, "/delta_to_delete.txt", []byte("delta"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Test 1: Lookup files before delete - _, err = ofs.LookupPath(ctx, "/base_to_delete.txt") - if err != nil { - t.Fatalf("LookupPath base file failed: %v", err) - } - _, err = ofs.LookupPath(ctx, "/delta_to_delete.txt") - if err != nil { - t.Fatalf("LookupPath delta file failed: %v", err) - } - - // Test 2: Delete base file (creates whiteout) - err = ofs.Unlink(ctx, "/base_to_delete.txt") - if err != nil { - t.Fatalf("Unlink base file failed: %v", err) - } - - // Test 3: Lookup should fail after delete - _, err = ofs.LookupPath(ctx, "/base_to_delete.txt") - if !IsNotExist(err) { - t.Error("Expected ENOENT after deleting base file") - } - - // Test 4: Delete delta file - err = ofs.Unlink(ctx, "/delta_to_delete.txt") - if err != nil { - t.Fatalf("Unlink delta file failed: %v", err) - } - _, err = ofs.LookupPath(ctx, "/delta_to_delete.txt") - if !IsNotExist(err) { - t.Error("Expected ENOENT after deleting delta file") - } - - // Test 5: Delete file in base directory - err = ofs.Unlink(ctx, "/base_dir/in_dir.txt") - if err != nil { - t.Fatalf("Unlink nested file failed: %v", err) - } - - // Test 6: Delete base directory (now empty) - err = ofs.Rmdir(ctx, "/base_dir") - if err != nil { - t.Fatalf("Rmdir base dir failed: %v", err) - } - _, err = ofs.LookupPath(ctx, "/base_dir") - if !IsNotExist(err) { - t.Error("Expected ENOENT after rmdir") - } - }) - } -} - -// TestOverlayCacheConsistency_RenameOperations tests rename operations -// produce identical results with and without caching. -func TestOverlayCacheConsistency_RenameOperations(t *testing.T) { - for _, cfg := range cacheConfigs { - t.Run(cfg.name, func(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayWithConfig(t, cfg) - defer afs.Close() - - // Setup - base.addFile(1, "base_rename.txt", 100, []byte("base rename"), 0o644) - err := ofs.WriteFile(ctx, "/delta_rename.txt", []byte("delta rename"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Lookup files multiple times to populate cache - for i := 0; i < 3; i++ { - _, _ = ofs.LookupPath(ctx, "/base_rename.txt") - _, _ = ofs.LookupPath(ctx, "/delta_rename.txt") - } - - // Test 1: Rename delta file - err = ofs.Rename(ctx, "/delta_rename.txt", "/delta_renamed.txt") - if err != nil { - t.Fatalf("Rename delta file failed: %v", err) - } - - // Test 2: Old path should not exist - _, err = ofs.LookupPath(ctx, "/delta_rename.txt") - if !IsNotExist(err) { - t.Error("Expected ENOENT for old delta path") - } - - // Test 3: New path should exist with correct content - content, err := ofs.ReadFile(ctx, "/delta_renamed.txt") - if err != nil { - t.Fatalf("ReadFile renamed delta failed: %v", err) - } - if string(content) != "delta rename" { - t.Errorf("Expected 'delta rename', got %q", string(content)) - } - - // Test 4: Rename base file (triggers copy-up) - err = ofs.Rename(ctx, "/base_rename.txt", "/base_renamed.txt") - if err != nil { - t.Fatalf("Rename base file failed: %v", err) - } - - // Test 5: Old path should not exist - _, err = ofs.LookupPath(ctx, "/base_rename.txt") - if !IsNotExist(err) { - t.Error("Expected ENOENT for old base path") - } - - // Test 6: New path should exist with correct content - content, err = ofs.ReadFile(ctx, "/base_renamed.txt") - if err != nil { - t.Fatalf("ReadFile renamed base failed: %v", err) - } - if string(content) != "base rename" { - t.Errorf("Expected 'base rename', got %q", string(content)) - } - }) - } -} - -// TestOverlayCacheConsistency_CopyUpOperations tests copy-up operations -// produce identical results with and without caching. -func TestOverlayCacheConsistency_CopyUpOperations(t *testing.T) { - for _, cfg := range cacheConfigs { - t.Run(cfg.name, func(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayWithConfig(t, cfg) - defer afs.Close() - - // Setup: Add file to base - base.addFile(1, "to_modify.txt", 100, []byte("original content"), 0o644) - - // Test 1: Read original content multiple times - for i := 0; i < 3; i++ { - content, err := ofs.ReadFile(ctx, "/to_modify.txt") - if err != nil { - t.Fatalf("ReadFile iteration %d failed: %v", i, err) - } - if string(content) != "original content" { - t.Errorf("Iteration %d: Expected 'original content', got %q", i, string(content)) - } - } - - // Test 2: Modify file (triggers copy-up) - err := ofs.WriteFile(ctx, "/to_modify.txt", []byte("modified content"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Test 3: Read modified content multiple times - for i := 0; i < 3; i++ { - content, err := ofs.ReadFile(ctx, "/to_modify.txt") - if err != nil { - t.Fatalf("ReadFile after modify iteration %d failed: %v", i, err) - } - if string(content) != "modified content" { - t.Errorf("Iteration %d: Expected 'modified content', got %q", i, string(content)) - } - } - - // Test 4: Chmod on base file (triggers copy-up) - base.addFile(1, "to_chmod.txt", 101, []byte("chmod test"), 0o644) - - // Read to populate cache - _, _ = ofs.LookupPath(ctx, "/to_chmod.txt") - _, _ = ofs.LookupPath(ctx, "/to_chmod.txt") - - err = ofs.Chmod(ctx, "/to_chmod.txt", 0o600) - if err != nil { - t.Fatalf("Chmod failed: %v", err) - } - - // Test 5: Verify chmod was applied - stats, err := ofs.LookupPath(ctx, "/to_chmod.txt") - if err != nil { - t.Fatalf("LookupPath after chmod failed: %v", err) - } - // Mode should include file type bits, check permission bits - if stats.Mode&0o777 != 0o600 { - t.Errorf("Expected mode 0600, got %o", stats.Mode&0o777) - } - }) - } -} - -// TestOverlayCacheConsistency_WhiteoutOperations tests whiteout behavior -// is consistent with and without caching. -func TestOverlayCacheConsistency_WhiteoutOperations(t *testing.T) { - for _, cfg := range cacheConfigs { - t.Run(cfg.name, func(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayWithConfig(t, cfg) - defer afs.Close() - - // Setup: Add files to base - base.addFile(1, "to_delete.txt", 100, []byte("will be deleted"), 0o644) - base.addDir(1, "dir_to_delete", 101, 0o755) - - // Test 1: Lookup files multiple times - for i := 0; i < 3; i++ { - _, err := ofs.LookupPath(ctx, "/to_delete.txt") - if err != nil { - t.Fatalf("LookupPath iteration %d failed: %v", i, err) - } - } - - // Test 2: Delete file (creates whiteout) - err := ofs.Unlink(ctx, "/to_delete.txt") - if err != nil { - t.Fatalf("Unlink failed: %v", err) - } - - // Test 3: Multiple lookups should all fail - for i := 0; i < 3; i++ { - _, err = ofs.LookupPath(ctx, "/to_delete.txt") - if !IsNotExist(err) { - t.Errorf("Iteration %d: Expected ENOENT after whiteout", i) - } - } - - // Test 4: Recreate file at whiteout location - err = ofs.WriteFile(ctx, "/to_delete.txt", []byte("recreated"), 0o644) - if err != nil { - t.Fatalf("WriteFile at whiteout location failed: %v", err) - } - - // Test 5: File should now exist with new content - for i := 0; i < 3; i++ { - content, err := ofs.ReadFile(ctx, "/to_delete.txt") - if err != nil { - t.Fatalf("ReadFile iteration %d failed: %v", i, err) - } - if string(content) != "recreated" { - t.Errorf("Iteration %d: Expected 'recreated', got %q", i, string(content)) - } - } - - // Test 6: Delete and recreate directory - err = ofs.Rmdir(ctx, "/dir_to_delete") - if err != nil { - t.Fatalf("Rmdir failed: %v", err) - } - _, err = ofs.LookupPath(ctx, "/dir_to_delete") - if !IsNotExist(err) { - t.Error("Expected ENOENT for deleted directory") - } - - err = ofs.Mkdir(ctx, "/dir_to_delete", 0o755) - if err != nil { - t.Fatalf("Mkdir at whiteout location failed: %v", err) - } - stats, err := ofs.LookupPath(ctx, "/dir_to_delete") - if err != nil { - t.Fatalf("LookupPath recreated dir failed: %v", err) - } - if !stats.IsDir() { - t.Error("Expected directory after mkdir") - } - }) - } -} - -// TestOverlayCacheConsistency_SymlinkOperations tests symlink operations -// are consistent with and without caching. -func TestOverlayCacheConsistency_SymlinkOperations(t *testing.T) { - for _, cfg := range cacheConfigs { - t.Run(cfg.name, func(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayWithConfig(t, cfg) - defer afs.Close() - - // Setup: Add symlink to base - base.addSymlink(1, "base_link", 100, "/target") - - // Test 1: Read base symlink multiple times - for i := 0; i < 3; i++ { - target, err := ofs.Readlink(ctx, "/base_link") - if err != nil { - t.Fatalf("Readlink iteration %d failed: %v", i, err) - } - if target != "/target" { - t.Errorf("Iteration %d: Expected '/target', got %q", i, target) - } - } - - // Test 2: Create delta symlink - err := ofs.Symlink(ctx, "/new_target", "/delta_link") - if err != nil { - t.Fatalf("Symlink failed: %v", err) - } - - // Test 3: Read delta symlink multiple times - for i := 0; i < 3; i++ { - target, err := ofs.Readlink(ctx, "/delta_link") - if err != nil { - t.Fatalf("Readlink delta iteration %d failed: %v", i, err) - } - if target != "/new_target" { - t.Errorf("Iteration %d: Expected '/new_target', got %q", i, target) - } - } - - // Test 4: Delete base symlink - err = ofs.Unlink(ctx, "/base_link") - if err != nil { - t.Fatalf("Unlink symlink failed: %v", err) - } - _, err = ofs.Readlink(ctx, "/base_link") - if !IsNotExist(err) { - t.Error("Expected ENOENT after deleting symlink") - } - }) - } -} - -// TestOverlayCacheConsistency_DeepPathResolution tests deep path resolution -// is consistent with and without caching. -func TestOverlayCacheConsistency_DeepPathResolution(t *testing.T) { - for _, cfg := range cacheConfigs { - t.Run(cfg.name, func(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayWithConfig(t, cfg) - defer afs.Close() - - // Setup: Create deep directory structure in base - // /a/b/c/d/e/file.txt - base.addDir(1, "a", 100, 0o755) - base.addDir(100, "b", 101, 0o755) - base.addDir(101, "c", 102, 0o755) - base.addDir(102, "d", 103, 0o755) - base.addDir(103, "e", 104, 0o755) - base.addFile(104, "file.txt", 105, []byte("deep file"), 0o644) - - // Test 1: Lookup deep path multiple times - for i := 0; i < 5; i++ { - content, err := ofs.ReadFile(ctx, "/a/b/c/d/e/file.txt") - if err != nil { - t.Fatalf("ReadFile deep path iteration %d failed: %v", i, err) - } - if string(content) != "deep file" { - t.Errorf("Iteration %d: Expected 'deep file', got %q", i, string(content)) - } - } - - // Test 2: Lookup intermediate paths - paths := []string{"/a", "/a/b", "/a/b/c", "/a/b/c/d", "/a/b/c/d/e"} - for _, p := range paths { - for i := 0; i < 3; i++ { - stats, err := ofs.LookupPath(ctx, p) - if err != nil { - t.Fatalf("LookupPath %s iteration %d failed: %v", p, i, err) - } - if !stats.IsDir() { - t.Errorf("Expected %s to be a directory", p) - } - } - } - - // Test 3: Create deep path in delta - err := ofs.MkdirAll(ctx, "/x/y/z", 0o755) - if err != nil { - t.Fatalf("MkdirAll failed: %v", err) - } - err = ofs.WriteFile(ctx, "/x/y/z/delta_file.txt", []byte("delta deep"), 0o644) - if err != nil { - t.Fatalf("WriteFile in deep delta path failed: %v", err) - } - - // Test 4: Read from deep delta path multiple times - for i := 0; i < 5; i++ { - content, err := ofs.ReadFile(ctx, "/x/y/z/delta_file.txt") - if err != nil { - t.Fatalf("ReadFile delta deep path iteration %d failed: %v", i, err) - } - if string(content) != "delta deep" { - t.Errorf("Iteration %d: Expected 'delta deep', got %q", i, string(content)) - } - } - }) - } -} - -// TestOverlayCacheConsistency_ReaddirMerging tests readdir merging -// is consistent with and without caching. -func TestOverlayCacheConsistency_ReaddirMerging(t *testing.T) { - for _, cfg := range cacheConfigs { - t.Run(cfg.name, func(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayWithConfig(t, cfg) - defer afs.Close() - - // Setup: Add files to both layers - base.addFile(1, "base1.txt", 100, []byte("b1"), 0o644) - base.addFile(1, "base2.txt", 101, []byte("b2"), 0o644) - base.addFile(1, "common.txt", 102, []byte("base common"), 0o644) - - ofs.WriteFile(ctx, "/delta1.txt", []byte("d1"), 0o644) - ofs.WriteFile(ctx, "/delta2.txt", []byte("d2"), 0o644) - ofs.WriteFile(ctx, "/common.txt", []byte("delta common"), 0o644) // Override base - - // Test 1: Readdir should merge both layers - entries, err := ofs.Readdir(ctx, 1) - if err != nil { - t.Fatalf("Readdir failed: %v", err) - } - - expected := []string{"base1.txt", "base2.txt", "common.txt", "delta1.txt", "delta2.txt"} - for _, name := range expected { - if !containsEntry(entries, name) { - t.Errorf("Expected %q in readdir results", name) - } - } - - // Test 2: common.txt should have delta content - content, err := ofs.ReadFile(ctx, "/common.txt") - if err != nil { - t.Fatalf("ReadFile common.txt failed: %v", err) - } - if string(content) != "delta common" { - t.Errorf("Expected 'delta common', got %q", string(content)) - } - - // Test 3: Delete base file - err = ofs.Unlink(ctx, "/base1.txt") - if err != nil { - t.Fatalf("Unlink base1.txt failed: %v", err) - } - - // Test 4: Readdir should not include deleted file - entries, err = ofs.Readdir(ctx, 1) - if err != nil { - t.Fatalf("Readdir after delete failed: %v", err) - } - if containsEntry(entries, "base1.txt") { - t.Error("Deleted file should not appear in readdir") - } - }) - } -} - -func containsEntry(entries []string, name string) bool { - for _, e := range entries { - if e == name { - return true - } - } - return false -} diff --git a/sdk/go/pool_test.go b/sdk/go/pool_test.go deleted file mode 100644 index 28bd1240..00000000 --- a/sdk/go/pool_test.go +++ /dev/null @@ -1,169 +0,0 @@ -package agentfs - -import ( - "context" - "testing" - "time" -) - -func TestPoolOptions_MaxOpenConns(t *testing.T) { - ctx := context.Background() - afs, err := Open(ctx, AgentFSOptions{ - Path: ":memory:", - Pool: PoolOptions{ - MaxOpenConns: 1, - }, - }) - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer afs.Close() - - // Verify database works with pool settings - err = afs.FS.WriteFile(ctx, "/test.txt", []byte("hello"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - data, err := afs.FS.ReadFile(ctx, "/test.txt") - if err != nil { - t.Fatalf("ReadFile failed: %v", err) - } - if string(data) != "hello" { - t.Errorf("Expected 'hello', got %q", data) - } -} - -func TestPoolOptions_MaxIdleConns(t *testing.T) { - ctx := context.Background() - afs, err := Open(ctx, AgentFSOptions{ - Path: ":memory:", - Pool: PoolOptions{ - MaxOpenConns: 5, - MaxIdleConns: 5, - }, - }) - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer afs.Close() - - // Verify database works with pool settings - err = afs.FS.WriteFile(ctx, "/test.txt", []byte("hello"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } -} - -func TestPoolOptions_ConnMaxLifetime(t *testing.T) { - ctx := context.Background() - afs, err := Open(ctx, AgentFSOptions{ - Path: ":memory:", - Pool: PoolOptions{ - ConnMaxLifetime: 1 * time.Hour, - }, - }) - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer afs.Close() - - // Verify database works with pool settings - err = afs.FS.WriteFile(ctx, "/test.txt", []byte("hello"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } -} - -func TestPoolOptions_ConnMaxIdleTime(t *testing.T) { - ctx := context.Background() - afs, err := Open(ctx, AgentFSOptions{ - Path: ":memory:", - Pool: PoolOptions{ - ConnMaxIdleTime: 5 * time.Minute, - }, - }) - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer afs.Close() - - // Verify database works with pool settings - err = afs.FS.WriteFile(ctx, "/test.txt", []byte("hello"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } -} - -func TestPoolOptions_AllSettings(t *testing.T) { - ctx := context.Background() - afs, err := Open(ctx, AgentFSOptions{ - Path: ":memory:", - Pool: PoolOptions{ - MaxOpenConns: 2, - MaxIdleConns: 2, - ConnMaxLifetime: 30 * time.Minute, - ConnMaxIdleTime: 5 * time.Minute, - }, - }) - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer afs.Close() - - // Verify database works with all pool settings - for i := 0; i < 10; i++ { - err = afs.KV.Set(ctx, "key", i) - if err != nil { - t.Fatalf("KV.Set failed: %v", err) - } - - var val int - err = afs.KV.Get(ctx, "key", &val) - if err != nil { - t.Fatalf("KV.Get failed: %v", err) - } - if val != i { - t.Errorf("Expected %d, got %d", i, val) - } - } -} - -func TestPoolOptions_DefaultsWhenZero(t *testing.T) { - ctx := context.Background() - - // All zero values - should use database/sql defaults - afs, err := Open(ctx, AgentFSOptions{ - Path: ":memory:", - Pool: PoolOptions{}, // All zero - }) - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer afs.Close() - - // Verify database works with default settings - err = afs.FS.WriteFile(ctx, "/test.txt", []byte("hello"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } -} - -func TestPoolOptions_NotSpecified(t *testing.T) { - ctx := context.Background() - - // Pool not specified at all - afs, err := Open(ctx, AgentFSOptions{ - Path: ":memory:", - }) - if err != nil { - t.Fatalf("Open failed: %v", err) - } - defer afs.Close() - - // Verify database works without pool settings - err = afs.FS.WriteFile(ctx, "/test.txt", []byte("hello"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } -} diff --git a/sdk/go/schema.go b/sdk/go/schema.go deleted file mode 100644 index 633b7b7d..00000000 --- a/sdk/go/schema.go +++ /dev/null @@ -1,369 +0,0 @@ -package agentfs - -// SQL schema definitions for AgentFS - -const schemaVersion = "0.4" - -// Table creation statements -const ( - createFsConfigTable = ` - CREATE TABLE IF NOT EXISTS fs_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL - )` - - createFsInodeTable = ` - CREATE TABLE IF NOT EXISTS fs_inode ( - ino INTEGER PRIMARY KEY AUTOINCREMENT, - mode INTEGER NOT NULL, - nlink INTEGER NOT NULL DEFAULT 0, - uid INTEGER NOT NULL DEFAULT 0, - gid INTEGER NOT NULL DEFAULT 0, - size INTEGER NOT NULL DEFAULT 0, - atime INTEGER NOT NULL, - mtime INTEGER NOT NULL, - ctime INTEGER NOT NULL, - rdev INTEGER NOT NULL DEFAULT 0 - )` - - createFsDentryTable = ` - CREATE TABLE IF NOT EXISTS fs_dentry ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - parent_ino INTEGER NOT NULL, - ino INTEGER NOT NULL, - UNIQUE(parent_ino, name) - )` - - createFsDentryIndex = ` - CREATE INDEX IF NOT EXISTS idx_fs_dentry_parent ON fs_dentry(parent_ino, name)` - - createFsDataTable = ` - CREATE TABLE IF NOT EXISTS fs_data ( - ino INTEGER NOT NULL, - chunk_index INTEGER NOT NULL, - data BLOB NOT NULL, - PRIMARY KEY (ino, chunk_index) - )` - - createFsSymlinkTable = ` - CREATE TABLE IF NOT EXISTS fs_symlink ( - ino INTEGER PRIMARY KEY, - target TEXT NOT NULL - )` - - createKvStoreTable = ` - CREATE TABLE IF NOT EXISTS kv_store ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - created_at INTEGER DEFAULT (unixepoch()), - updated_at INTEGER DEFAULT (unixepoch()) - )` - - createKvStoreIndex = ` - CREATE INDEX IF NOT EXISTS idx_kv_store_created_at ON kv_store(created_at)` - - createToolCallsTable = ` - CREATE TABLE IF NOT EXISTS tool_calls ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - parameters TEXT, - result TEXT, - error TEXT, - started_at INTEGER NOT NULL, - completed_at INTEGER NOT NULL, - duration_ms INTEGER NOT NULL - )` - - createToolCallsNameIndex = ` - CREATE INDEX IF NOT EXISTS idx_tool_calls_name ON tool_calls(name)` - - createToolCallsStartedAtIndex = ` - CREATE INDEX IF NOT EXISTS idx_tool_calls_started_at ON tool_calls(started_at)` - - // Overlay filesystem tables (optional) - createFsWhiteoutTable = ` - CREATE TABLE IF NOT EXISTS fs_whiteout ( - path TEXT PRIMARY KEY, - parent_path TEXT NOT NULL, - created_at INTEGER NOT NULL - )` - - createFsWhiteoutIndex = ` - CREATE INDEX IF NOT EXISTS idx_fs_whiteout_parent ON fs_whiteout(parent_path)` - - createFsOriginTable = ` - CREATE TABLE IF NOT EXISTS fs_origin ( - delta_ino INTEGER PRIMARY KEY, - base_ino INTEGER NOT NULL - )` -) - -// allSchemaStatements returns all schema creation statements in order -func allSchemaStatements() []string { - return []string{ - createFsConfigTable, - createFsInodeTable, - createFsDentryTable, - createFsDentryIndex, - createFsDataTable, - createFsSymlinkTable, - createKvStoreTable, - createKvStoreIndex, - createToolCallsTable, - createToolCallsNameIndex, - createToolCallsStartedAtIndex, - createFsWhiteoutTable, - createFsWhiteoutIndex, - createFsOriginTable, - } -} - -// Nanosecond timestamp migrations (backwards-compatible) -// These use ALTER TABLE which will fail silently if columns already exist -const ( - migrateAddAtimeNsec = `ALTER TABLE fs_inode ADD COLUMN atime_nsec INTEGER NOT NULL DEFAULT 0` - migrateAddMtimeNsec = `ALTER TABLE fs_inode ADD COLUMN mtime_nsec INTEGER NOT NULL DEFAULT 0` - migrateAddCtimeNsec = `ALTER TABLE fs_inode ADD COLUMN ctime_nsec INTEGER NOT NULL DEFAULT 0` -) - -// nsecMigrations returns the nanosecond column migration statements -func nsecMigrations() []string { - return []string{ - migrateAddAtimeNsec, - migrateAddMtimeNsec, - migrateAddCtimeNsec, - } -} - -// Initialization queries -const ( - initFsConfig = ` - INSERT OR IGNORE INTO fs_config (key, value) VALUES ('chunk_size', ?)` - - initSchemaVersion = ` - INSERT OR IGNORE INTO fs_config (key, value) VALUES ('schema_version', ?)` - - getSchemaVersion = ` - SELECT value FROM fs_config WHERE key = 'schema_version'` - - initRootInode = ` - INSERT OR IGNORE INTO fs_inode (ino, mode, nlink, uid, gid, size, atime, mtime, ctime) - VALUES (1, ?, 1, 0, 0, 0, unixepoch(), unixepoch(), unixepoch())` - - getChunkSize = ` - SELECT value FROM fs_config WHERE key = 'chunk_size'` -) - -// Filesystem queries -const ( - // Path resolution - queryDentryByParentAndName = ` - SELECT ino FROM fs_dentry WHERE parent_ino = ? AND name = ?` - - // Combined dentry + inode lookup (avoids two round-trips per component) - queryDentryWithMode = ` - SELECT d.ino, i.mode FROM fs_dentry d - JOIN fs_inode i ON d.ino = i.ino - WHERE d.parent_ino = ? AND d.name = ?` - - // Inode operations - queryInodeByIno = ` - SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime, rdev, - atime_nsec, mtime_nsec, ctime_nsec - FROM fs_inode WHERE ino = ?` - - insertInode = ` - INSERT INTO fs_inode (mode, nlink, uid, gid, size, atime, mtime, ctime, rdev, - atime_nsec, mtime_nsec, ctime_nsec) - VALUES (?, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - RETURNING ino` - - updateInodeSize = ` - UPDATE fs_inode SET size = ?, mtime = ?, mtime_nsec = ? WHERE ino = ?` - - updateInodeTimes = ` - UPDATE fs_inode SET atime = ?, mtime = ?, ctime = ?, - atime_nsec = ?, mtime_nsec = ?, ctime_nsec = ? - WHERE ino = ?` - - updateInodeAtime = ` - UPDATE fs_inode SET atime = ?, atime_nsec = ? WHERE ino = ?` - - updateInodeMode = ` - UPDATE fs_inode SET mode = ?, ctime = ?, ctime_nsec = ? WHERE ino = ?` - - incrementNlink = ` - UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?` - - decrementNlink = ` - UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?` - - deleteInode = ` - DELETE FROM fs_inode WHERE ino = ?` - - // Directory entry operations - insertDentry = ` - INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)` - - deleteDentry = ` - DELETE FROM fs_dentry WHERE parent_ino = ? AND name = ?` - - queryDentriesByParent = ` - SELECT name FROM fs_dentry WHERE parent_ino = ? ORDER BY name ASC` - - queryDentriesPlusByParent = ` - SELECT d.name, i.ino, i.mode, i.nlink, i.uid, i.gid, i.size, i.atime, i.mtime, i.ctime, i.rdev, - i.atime_nsec, i.mtime_nsec, i.ctime_nsec - FROM fs_dentry d - JOIN fs_inode i ON d.ino = i.ino - WHERE d.parent_ino = ? - ORDER BY d.name ASC` - - countDentriesByParent = ` - SELECT COUNT(*) FROM fs_dentry WHERE parent_ino = ?` - - updateDentryParent = ` - UPDATE fs_dentry SET parent_ino = ?, name = ? WHERE parent_ino = ? AND name = ?` - - // File data operations - insertChunk = ` - INSERT OR REPLACE INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?)` - - queryChunksByIno = ` - SELECT data FROM fs_data WHERE ino = ? ORDER BY chunk_index ASC` - - queryChunkRange = ` - SELECT chunk_index, data FROM fs_data - WHERE ino = ? AND chunk_index >= ? AND chunk_index <= ? - ORDER BY chunk_index ASC` - - deleteChunksByIno = ` - DELETE FROM fs_data WHERE ino = ?` - - deleteChunksFromIndex = ` - DELETE FROM fs_data WHERE ino = ? AND chunk_index >= ?` - - // Symlink operations - insertSymlink = ` - INSERT INTO fs_symlink (ino, target) VALUES (?, ?)` - - querySymlinkTarget = ` - SELECT target FROM fs_symlink WHERE ino = ?` - - deleteSymlink = ` - DELETE FROM fs_symlink WHERE ino = ?` - - // Filesystem statistics - statfsInodeCount = ` - SELECT COUNT(*) FROM fs_inode` - - statfsBytesUsed = ` - SELECT COALESCE(SUM(size), 0) FROM fs_inode` -) - -// Key-value store queries -const ( - kvSet = ` - INSERT INTO kv_store (key, value, updated_at) - VALUES (?, ?, unixepoch()) - ON CONFLICT(key) DO UPDATE SET - value = excluded.value, - updated_at = unixepoch()` - - kvGet = ` - SELECT value FROM kv_store WHERE key = ?` - - kvDelete = ` - DELETE FROM kv_store WHERE key = ?` - - kvHas = ` - SELECT 1 FROM kv_store WHERE key = ? LIMIT 1` - - kvKeys = ` - SELECT key FROM kv_store ORDER BY key ASC` - - kvKeysWithPrefix = ` - SELECT key FROM kv_store WHERE key LIKE ? ESCAPE '\' ORDER BY key ASC` - - kvList = ` - SELECT key, created_at, updated_at FROM kv_store ORDER BY key ASC` - - kvListWithPrefix = ` - SELECT key, created_at, updated_at FROM kv_store WHERE key LIKE ? ESCAPE '\' ORDER BY key ASC` - - kvClear = ` - DELETE FROM kv_store` - - kvClearWithPrefix = ` - DELETE FROM kv_store WHERE key LIKE ? ESCAPE '\'` -) - -// Tool calls queries -const ( - toolCallsInsert = ` - INSERT INTO tool_calls (name, parameters, result, error, started_at, completed_at, duration_ms) - VALUES (?, ?, ?, ?, ?, ?, ?) - RETURNING id` - - toolCallsGetByID = ` - SELECT id, name, parameters, result, error, started_at, completed_at, duration_ms - FROM tool_calls WHERE id = ?` - - toolCallsGetByName = ` - SELECT id, name, parameters, result, error, started_at, completed_at, duration_ms - FROM tool_calls WHERE name = ? - ORDER BY started_at DESC - LIMIT ?` - - toolCallsGetRecent = ` - SELECT id, name, parameters, result, error, started_at, completed_at, duration_ms - FROM tool_calls WHERE started_at > ? - ORDER BY started_at DESC - LIMIT ?` - - toolCallsGetStats = ` - SELECT - name, - COUNT(*) as total_calls, - SUM(CASE WHEN error IS NULL THEN 1 ELSE 0 END) as successful, - SUM(CASE WHEN error IS NOT NULL THEN 1 ELSE 0 END) as failed, - AVG(duration_ms) as avg_duration_ms - FROM tool_calls - GROUP BY name - ORDER BY total_calls DESC` -) - -// Overlay filesystem queries -const ( - // Whiteout operations - whiteoutInsert = ` - INSERT OR REPLACE INTO fs_whiteout (path, parent_path, created_at) - VALUES (?, ?, ?)` - - whiteoutDelete = ` - DELETE FROM fs_whiteout WHERE path = ?` - - whiteoutCheck = ` - SELECT 1 FROM fs_whiteout WHERE path = ? LIMIT 1` - - whiteoutList = ` - SELECT path FROM fs_whiteout` - - whiteoutListByParent = ` - SELECT path FROM fs_whiteout WHERE parent_path = ?` - - // Origin tracking operations - originInsert = ` - INSERT OR REPLACE INTO fs_origin (delta_ino, base_ino) - VALUES (?, ?)` - - originGet = ` - SELECT base_ino FROM fs_origin WHERE delta_ino = ?` - - originList = ` - SELECT delta_ino, base_ino FROM fs_origin` - - originDelete = ` - DELETE FROM fs_origin WHERE delta_ino = ?` -) diff --git a/sdk/go/spec_test.go b/sdk/go/spec_test.go deleted file mode 100644 index 8736fb65..00000000 --- a/sdk/go/spec_test.go +++ /dev/null @@ -1,2954 +0,0 @@ -package agentfs - -import ( - "context" - "database/sql" - "encoding/json" - "testing" - "time" -) - -// ============================================================================= -// Spec Test Helpers -// ============================================================================= - -func setupSpecTest(t *testing.T) (*AgentFS, context.Context) { - t.Helper() - ctx := context.Background() - afs, err := Open(ctx, AgentFSOptions{Path: ":memory:"}) - if err != nil { - t.Fatalf("Failed to open AgentFS: %v", err) - } - return afs, ctx -} - -// tableExists checks if a table exists in the database -func tableExists(t *testing.T, db *sql.DB, tableName string) bool { - t.Helper() - var count int - err := db.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?", tableName).Scan(&count) - if err != nil { - t.Fatalf("Failed to check table existence: %v", err) - } - return count > 0 -} - -// indexExists checks if an index exists in the database -func indexExists(t *testing.T, db *sql.DB, indexName string) bool { - t.Helper() - var count int - err := db.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name=?", indexName).Scan(&count) - if err != nil { - t.Fatalf("Failed to check index existence: %v", err) - } - return count > 0 -} - -// columnExists checks if a column exists in a table -func columnExists(t *testing.T, db *sql.DB, tableName, columnName string) bool { - t.Helper() - rows, err := db.Query("PRAGMA table_info(" + tableName + ")") - if err != nil { - t.Fatalf("Failed to get table info: %v", err) - } - defer rows.Close() - - for rows.Next() { - var cid int - var name, ctype string - var notnull, pk int - var dflt sql.NullString - if err := rows.Scan(&cid, &name, &ctype, ¬null, &dflt, &pk); err != nil { - t.Fatalf("Failed to scan column info: %v", err) - } - if name == columnName { - return true - } - } - return false -} - -// ============================================================================= -// Tool Calls (TC-1 to TC-15) -// ============================================================================= - -// ============================================================================= -// Tool Calls (TC-1 to TC-15) -// ============================================================================= - -// TC-1: Table tool_calls has all required columns with correct types -func TestSpec_TC1_ToolCallsTableExists(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !tableExists(t, afs.db, "tool_calls") { - t.Fatal("Table tool_calls does not exist") - } - - requiredColumns := []string{"id", "name", "parameters", "result", "error", "started_at", "completed_at", "duration_ms"} - for _, col := range requiredColumns { - if !columnExists(t, afs.db, "tool_calls", col) { - t.Errorf("Column %s missing from tool_calls table", col) - } - } -} - -// TC-2: Index idx_tool_calls_name exists on name column -func TestSpec_TC2_ToolCallsNameIndex(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !indexExists(t, afs.db, "idx_tool_calls_name") { - t.Fatal("Index idx_tool_calls_name does not exist") - } -} - -// TC-3: Index idx_tool_calls_started_at exists on started_at column -func TestSpec_TC3_ToolCallsStartedAtIndex(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !indexExists(t, afs.db, "idx_tool_calls_started_at") { - t.Fatal("Index idx_tool_calls_started_at does not exist") - } -} - -// TC-4: id is auto-incrementing primary key -func TestSpec_TC4_ToolCallsAutoIncrement(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Insert two tool calls and verify IDs auto-increment - now := time.Now().Unix() - tc1, err := afs.Tools.Record(ctx, "test1", nil, "result1", nil, now, now) - if err != nil { - t.Fatalf("Failed to record tool call: %v", err) - } - - tc2, err := afs.Tools.Record(ctx, "test2", nil, "result2", nil, now, now) - if err != nil { - t.Fatalf("Failed to record tool call: %v", err) - } - - if tc2.ID <= tc1.ID { - t.Errorf("Expected id2 (%d) > id1 (%d)", tc2.ID, tc1.ID) - } -} - -// TC-5: Exactly one of result or error SHOULD be non-NULL -func TestSpec_TC5_ResultErrorMutualExclusion(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - now := time.Now().Unix() - - // Success case: result set, error nil - tc1, err := afs.Tools.Record(ctx, "success", nil, `{"ok":true}`, nil, now, now) - if err != nil { - t.Fatalf("Failed to record success: %v", err) - } - - tc1, err = afs.Tools.Get(ctx, tc1.ID) - if err != nil { - t.Fatalf("Failed to get tool call: %v", err) - } - if len(tc1.Result) == 0 || tc1.Error != nil { - t.Error("Success case: expected result set, error nil") - } - - // Error case: error set, result empty - errMsg := "something failed" - tc2, err := afs.Tools.Record(ctx, "failure", nil, nil, &errMsg, now, now) - if err != nil { - t.Fatalf("Failed to record failure: %v", err) - } - - tc2, err = afs.Tools.Get(ctx, tc2.ID) - if err != nil { - t.Fatalf("Failed to get tool call: %v", err) - } - if tc2.Error == nil || len(tc2.Result) != 0 { - t.Error("Error case: expected error set, result empty") - } -} - -// TC-6: completed_at MUST always be set -func TestSpec_TC6_CompletedAtRequired(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - now := time.Now().Unix() - tc, err := afs.Tools.Record(ctx, "test", nil, "result", nil, now, now) - if err != nil { - t.Fatalf("Failed to record: %v", err) - } - - tc, err = afs.Tools.Get(ctx, tc.ID) - if err != nil { - t.Fatalf("Failed to get: %v", err) - } - - if tc.CompletedAt == 0 { - t.Error("completed_at should be set") - } -} - -// TC-7: duration_ms MUST equal (completed_at - started_at) * 1000 -func TestSpec_TC7_DurationCalculation(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - startedAt := time.Now().Unix() - completedAt := startedAt + 5 // 5 seconds later - expectedDuration := int64(5000) - - tc, err := afs.Tools.Record(ctx, "test", nil, "result", nil, startedAt, completedAt) - if err != nil { - t.Fatalf("Failed to record: %v", err) - } - - tc, err = afs.Tools.Get(ctx, tc.ID) - if err != nil { - t.Fatalf("Failed to get: %v", err) - } - - if tc.DurationMs != expectedDuration { - t.Errorf("Expected duration %d, got %d", expectedDuration, tc.DurationMs) - } -} - -// TC-8: parameters MUST be valid JSON string when present -func TestSpec_TC8_ParametersValidJSON(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - params := map[string]interface{}{"key": "value", "num": 42} - now := time.Now().Unix() - - tc, err := afs.Tools.Record(ctx, "test", params, "result", nil, now, now) - if err != nil { - t.Fatalf("Failed to record: %v", err) - } - - tc, err = afs.Tools.Get(ctx, tc.ID) - if err != nil { - t.Fatalf("Failed to get: %v", err) - } - - // Verify stored parameters is valid JSON - var parsed map[string]interface{} - if err := json.Unmarshal(tc.Parameters, &parsed); err != nil { - t.Errorf("Parameters not valid JSON: %v", err) - } -} - -// TC-9: result MUST be valid JSON string when present -func TestSpec_TC9_ResultValidJSON(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - result := map[string]interface{}{"status": "ok", "count": 42} - now := time.Now().Unix() - - tc, err := afs.Tools.Record(ctx, "test", nil, result, nil, now, now) - if err != nil { - t.Fatalf("Failed to record: %v", err) - } - - tc, err = afs.Tools.Get(ctx, tc.ID) - if err != nil { - t.Fatalf("Failed to get: %v", err) - } - - // Verify stored result is valid JSON - var parsed map[string]interface{} - if err := json.Unmarshal(tc.Result, &parsed); err != nil { - t.Errorf("Result not valid JSON: %v", err) - } -} - -// TC-10: Records MUST NOT be updated after insertion (insert-only audit log) -func TestSpec_TC10_RecordsNotUpdated(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - now := time.Now().Unix() - tc, err := afs.Tools.Record(ctx, "test", nil, "result", nil, now, now) - if err != nil { - t.Fatalf("Failed to record: %v", err) - } - - // Attempt to update should fail or have no effect - // Try direct SQL update - should be blocked by design or ignored - _, err = afs.db.ExecContext(ctx, "UPDATE tool_calls SET name = 'modified' WHERE id = ?", tc.ID) - // Note: SQLite doesn't prevent updates, but the API shouldn't expose update methods - // This test verifies the API doesn't provide an Update method - - // Re-fetch and verify original data - tc2, err := afs.Tools.Get(ctx, tc.ID) - if err != nil { - t.Fatalf("Failed to get: %v", err) - } - - // If update succeeded at SQL level, that's a schema concern - // The spec says SHOULD be insert-only; verify API doesn't expose updates - if tc2.Name != tc.Name && tc2.Name != "modified" { - t.Error("Record was unexpectedly modified") - } -} - -// TC-11: Records MUST NOT be deleted (insert-only audit log) -func TestSpec_TC11_RecordsNotDeleted(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - now := time.Now().Unix() - tc, err := afs.Tools.Record(ctx, "test", nil, "result", nil, now, now) - if err != nil { - t.Fatalf("Failed to record: %v", err) - } - - // Verify record exists - _, err = afs.Tools.Get(ctx, tc.ID) - if err != nil { - t.Fatalf("Record should exist: %v", err) - } - - // The API should not expose a Delete method - // This is verified by the absence of such a method on ToolCalls - // Record should still be retrievable - tc2, err := afs.Tools.Get(ctx, tc.ID) - if err != nil { - t.Error("Record should still exist - API should not allow deletion") - } - if tc2.ID != tc.ID { - t.Error("Record ID should match") - } -} - -// TC-12: Record tool call inserts correctly with all fields -func TestSpec_TC12_RecordToolCall(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - params := map[string]string{"arg": "value"} - startedAt := time.Now().Unix() - completedAt := startedAt + 1 - - tc, err := afs.Tools.Record(ctx, "my_tool", params, `{"success":true}`, nil, startedAt, completedAt) - if err != nil { - t.Fatalf("Failed to record: %v", err) - } - - tc, err = afs.Tools.Get(ctx, tc.ID) - if err != nil { - t.Fatalf("Failed to get: %v", err) - } - - if tc.Name != "my_tool" { - t.Errorf("Expected name 'my_tool', got %q", tc.Name) - } - if tc.StartedAt != startedAt { - t.Errorf("Expected started_at %d, got %d", startedAt, tc.StartedAt) - } - if tc.CompletedAt != completedAt { - t.Errorf("Expected completed_at %d, got %d", completedAt, tc.CompletedAt) - } -} - -// TC-13: Query by name returns correct records ordered by started_at DESC -func TestSpec_TC13_QueryByName(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - now := time.Now().Unix() - - // Insert multiple tool calls with same name at different times - _, _ = afs.Tools.Record(ctx, "target", nil, "r1", nil, now, now) - _, _ = afs.Tools.Record(ctx, "other", nil, "r2", nil, now+1, now+1) - _, _ = afs.Tools.Record(ctx, "target", nil, "r3", nil, now+2, now+2) - - calls, err := afs.Tools.GetByName(ctx, "target", 10) - if err != nil { - t.Fatalf("Failed to query: %v", err) - } - - if len(calls) != 2 { - t.Fatalf("Expected 2 calls, got %d", len(calls)) - } - - // Should be ordered by started_at DESC (most recent first) - if calls[0].StartedAt < calls[1].StartedAt { - t.Error("Results should be ordered by started_at DESC") - } -} - -// TC-14: Query recent tool calls filters by started_at correctly -func TestSpec_TC14_QueryRecent(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - baseTime := time.Now().Unix() - 100 - - // Insert tool calls at different times - _, _ = afs.Tools.Record(ctx, "old", nil, "r1", nil, baseTime, baseTime) - _, _ = afs.Tools.Record(ctx, "recent1", nil, "r2", nil, baseTime+50, baseTime+50) - _, _ = afs.Tools.Record(ctx, "recent2", nil, "r3", nil, baseTime+60, baseTime+60) - - // Query recent (since baseTime+40) - calls, err := afs.Tools.GetRecent(ctx, baseTime+40, 10) - if err != nil { - t.Fatalf("Failed to query recent: %v", err) - } - - if len(calls) != 2 { - t.Errorf("Expected 2 recent calls, got %d", len(calls)) - } - - for _, c := range calls { - if c.StartedAt < baseTime+40 { - t.Errorf("Call started_at %d is before filter %d", c.StartedAt, baseTime+40) - } - } -} - -// TC-15: Performance analysis query groups by name with correct counts -func TestSpec_TC15_PerformanceStats(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - now := time.Now().Unix() - - // Insert multiple tool calls - _, _ = afs.Tools.Record(ctx, "tool_a", nil, "ok", nil, now, now+1) - _, _ = afs.Tools.Record(ctx, "tool_a", nil, "ok", nil, now, now+2) - errMsg := "failed" - _, _ = afs.Tools.Record(ctx, "tool_a", nil, nil, &errMsg, now, now+1) - _, _ = afs.Tools.Record(ctx, "tool_b", nil, "ok", nil, now, now+1) - - stats, err := afs.Tools.GetStats(ctx) - if err != nil { - t.Fatalf("Failed to get stats: %v", err) - } - - // Find tool_a stats - var toolAStats *ToolCallStats - for i := range stats { - if stats[i].Name == "tool_a" { - toolAStats = &stats[i] - break - } - } - - if toolAStats == nil { - t.Fatal("Stats for tool_a not found") - } - - if toolAStats.TotalCalls != 3 { - t.Errorf("Expected 3 total calls for tool_a, got %d", toolAStats.TotalCalls) - } - if toolAStats.Successful != 2 { - t.Errorf("Expected 2 successful calls for tool_a, got %d", toolAStats.Successful) - } - if toolAStats.Failed != 1 { - t.Errorf("Expected 1 failed call for tool_a, got %d", toolAStats.Failed) - } -} - -// ============================================================================= -// Virtual Filesystem (FS-1 to FS-69) -// ============================================================================= - -// FS-1: Table fs_config exists with correct columns -func TestSpec_FS1_FsConfigTable(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !tableExists(t, afs.db, "fs_config") { - t.Fatal("Table fs_config does not exist") - } - if !columnExists(t, afs.db, "fs_config", "key") { - t.Error("Column 'key' missing") - } - if !columnExists(t, afs.db, "fs_config", "value") { - t.Error("Column 'value' missing") - } -} - -// FS-2: Table fs_inode has required columns -func TestSpec_FS2_FsInodeTable(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !tableExists(t, afs.db, "fs_inode") { - t.Fatal("Table fs_inode does not exist") - } - - requiredColumns := []string{"ino", "mode", "nlink", "uid", "gid", "size", "atime", "mtime", "ctime"} - for _, col := range requiredColumns { - if !columnExists(t, afs.db, "fs_inode", col) { - t.Errorf("Column %s missing from fs_inode", col) - } - } -} - -// FS-3: Table fs_inode has nlink column (v0.2) -func TestSpec_FS3_FsInodeNlink(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !columnExists(t, afs.db, "fs_inode", "nlink") { - t.Error("Column 'nlink' missing from fs_inode") - } -} - -// FS-4: Table fs_inode has rdev column (v0.4) -func TestSpec_FS4_FsInodeRdev(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !columnExists(t, afs.db, "fs_inode", "rdev") { - t.Error("Column 'rdev' missing from fs_inode") - } -} - -// FS-5: Table fs_inode has nanosecond columns (v0.4) -func TestSpec_FS5_FsInodeNanoseconds(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - nsecColumns := []string{"atime_nsec", "mtime_nsec", "ctime_nsec"} - for _, col := range nsecColumns { - if !columnExists(t, afs.db, "fs_inode", col) { - t.Errorf("Column %s missing from fs_inode", col) - } - } -} - -// FS-6: Table fs_dentry has correct columns and unique constraint -func TestSpec_FS6_FsDentryTable(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !tableExists(t, afs.db, "fs_dentry") { - t.Fatal("Table fs_dentry does not exist") - } - - requiredColumns := []string{"id", "name", "parent_ino", "ino"} - for _, col := range requiredColumns { - if !columnExists(t, afs.db, "fs_dentry", col) { - t.Errorf("Column %s missing from fs_dentry", col) - } - } -} - -// FS-7: Index idx_fs_dentry_parent exists -func TestSpec_FS7_FsDentryIndex(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !indexExists(t, afs.db, "idx_fs_dentry_parent") { - t.Error("Index idx_fs_dentry_parent does not exist") - } -} - -// FS-8: Table fs_data has correct primary key -func TestSpec_FS8_FsDataTable(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !tableExists(t, afs.db, "fs_data") { - t.Fatal("Table fs_data does not exist") - } - - requiredColumns := []string{"ino", "chunk_index", "data"} - for _, col := range requiredColumns { - if !columnExists(t, afs.db, "fs_data", col) { - t.Errorf("Column %s missing from fs_data", col) - } - } -} - -// FS-9: Table fs_symlink has correct columns -func TestSpec_FS9_FsSymlinkTable(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !tableExists(t, afs.db, "fs_symlink") { - t.Fatal("Table fs_symlink does not exist") - } - - if !columnExists(t, afs.db, "fs_symlink", "ino") { - t.Error("Column 'ino' missing") - } - if !columnExists(t, afs.db, "fs_symlink", "target") { - t.Error("Column 'target' missing") - } -} - -// FS-10: chunk_size configuration exists with default value 4096 -func TestSpec_FS10_ChunkSizeDefault(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - var value string - err := afs.db.QueryRow("SELECT value FROM fs_config WHERE key = 'chunk_size'").Scan(&value) - if err != nil { - t.Fatalf("Failed to get chunk_size: %v", err) - } - - if value != "4096" { - t.Errorf("Expected chunk_size '4096', got %q", value) - } -} - -// FS-11: Configuration is immutable after initialization -func TestSpec_FS11_ConfigImmutable(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Get initial chunk_size - var initialValue string - err := afs.db.QueryRowContext(ctx, "SELECT value FROM fs_config WHERE key = 'chunk_size'").Scan(&initialValue) - if err != nil { - t.Fatalf("Failed to get initial chunk_size: %v", err) - } - - // Attempt to change chunk_size (should be prevented or ignored on re-open) - // The spec says configuration is immutable after initialization - // This is enforced by INSERT OR IGNORE in initFsConfig - - // Try to update directly - _, _ = afs.db.ExecContext(ctx, "UPDATE fs_config SET value = '8192' WHERE key = 'chunk_size'") - - // Re-read - if update worked, it's a violation - // But more importantly, reopening should preserve original - var currentValue string - err = afs.db.QueryRowContext(ctx, "SELECT value FROM fs_config WHERE key = 'chunk_size'").Scan(¤tValue) - if err != nil { - t.Fatalf("Failed to get current chunk_size: %v", err) - } - - // The filesystem subsystem should use the original value - if afs.FS.chunkSize != 4096 { - t.Errorf("Filesystem chunkSize should remain 4096, got %d", afs.FS.chunkSize) - } -} - -// FS-12: S_IFMT correctly masks file type -func TestSpec_FS12_SIFMT(t *testing.T) { - if S_IFMT != 0o170000 { - t.Errorf("S_IFMT should be 0o170000, got %o", S_IFMT) - } -} - -// FS-13: S_IFREG identifies regular files -func TestSpec_FS13_SIFREG(t *testing.T) { - if S_IFREG != 0o100000 { - t.Errorf("S_IFREG should be 0o100000, got %o", S_IFREG) - } - - mode := int64(S_IFREG | 0o644) - if mode&S_IFMT != S_IFREG { - t.Error("S_IFREG not correctly identified") - } -} - -// FS-14: S_IFDIR identifies directories -func TestSpec_FS14_SIFDIR(t *testing.T) { - if S_IFDIR != 0o040000 { - t.Errorf("S_IFDIR should be 0o040000, got %o", S_IFDIR) - } - - mode := int64(S_IFDIR | 0o755) - if mode&S_IFMT != S_IFDIR { - t.Error("S_IFDIR not correctly identified") - } -} - -// FS-15: S_IFLNK identifies symbolic links -func TestSpec_FS15_SIFLNK(t *testing.T) { - if S_IFLNK != 0o120000 { - t.Errorf("S_IFLNK should be 0o120000, got %o", S_IFLNK) - } -} - -// FS-16: S_IFIFO (0o010000) identifies FIFOs/named pipes -func TestSpec_FS16_SIFIFO(t *testing.T) { - if S_IFIFO != 0o010000 { - t.Errorf("S_IFIFO should be 0o010000, got %o", S_IFIFO) - } - - mode := int64(S_IFIFO | 0o644) - if mode&S_IFMT != S_IFIFO { - t.Error("S_IFIFO not correctly identified") - } -} - -// FS-17: S_IFCHR (0o020000) identifies character devices -func TestSpec_FS17_SIFCHR(t *testing.T) { - if S_IFCHR != 0o020000 { - t.Errorf("S_IFCHR should be 0o020000, got %o", S_IFCHR) - } - - mode := int64(S_IFCHR | 0o644) - if mode&S_IFMT != S_IFCHR { - t.Error("S_IFCHR not correctly identified") - } -} - -// FS-18: S_IFBLK (0o060000) identifies block devices -func TestSpec_FS18_SIFBLK(t *testing.T) { - if S_IFBLK != 0o060000 { - t.Errorf("S_IFBLK should be 0o060000, got %o", S_IFBLK) - } - - mode := int64(S_IFBLK | 0o644) - if mode&S_IFMT != S_IFBLK { - t.Error("S_IFBLK not correctly identified") - } -} - -// FS-19: S_IFSOCK (0o140000) identifies sockets -func TestSpec_FS19_SIFSOCK(t *testing.T) { - if S_IFSOCK != 0o140000 { - t.Errorf("S_IFSOCK should be 0o140000, got %o", S_IFSOCK) - } - - mode := int64(S_IFSOCK | 0o644) - if mode&S_IFMT != S_IFSOCK { - t.Error("S_IFSOCK not correctly identified") - } -} - -// FS-20: Permission bits correctly encode rwxrwxrwx -func TestSpec_FS20_PermissionBits(t *testing.T) { - mode := int64(S_IFREG | 0o755) - perms := mode & 0o777 - if perms != 0o755 { - t.Errorf("Expected perms 0o755, got %o", perms) - } -} - -// FS-21: Root inode (ino=1) is created on initialization -func TestSpec_FS21_RootInodeCreated(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - stats, err := afs.FS.Stat(ctx, "/") - if err != nil { - t.Fatalf("Failed to stat root: %v", err) - } - - if stats.Ino != 1 { - t.Errorf("Root inode should be 1, got %d", stats.Ino) - } -} - -// FS-22: Root inode has mode 0o040755 -func TestSpec_FS22_RootInodeMode(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - stats, err := afs.FS.Stat(ctx, "/") - if err != nil { - t.Fatalf("Failed to stat root: %v", err) - } - - expectedMode := int64(S_IFDIR | 0o755) - if stats.Mode != expectedMode { - t.Errorf("Root mode should be %o, got %o", expectedMode, stats.Mode) - } -} - -// FS-23: Root inode has nlink=1 -func TestSpec_FS23_RootInodeNlink(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - stats, err := afs.FS.Stat(ctx, "/") - if err != nil { - t.Fatalf("Failed to stat root: %v", err) - } - - if stats.Nlink < 1 { - t.Errorf("Root nlink should be >= 1, got %d", stats.Nlink) - } -} - -// FS-24: Root inode MUST always exist -func TestSpec_FS24_RootInodeMustExist(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - _, err := afs.FS.Stat(ctx, "/") - if err != nil { - t.Fatal("Root inode must always exist") - } -} - -// FS-25: Every dentry MUST reference a valid inode -func TestSpec_FS25_DentryReferencesValidInode(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create a file - err := afs.FS.WriteFile(ctx, "/test.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("Failed to write file: %v", err) - } - - // Query dentry and verify inode exists - var ino int64 - err = afs.db.QueryRowContext(ctx, - "SELECT ino FROM fs_dentry WHERE name = 'test.txt' AND parent_ino = 1").Scan(&ino) - if err != nil { - t.Fatalf("Failed to get dentry: %v", err) - } - - // Verify inode exists - var count int - err = afs.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM fs_inode WHERE ino = ?", ino).Scan(&count) - if err != nil { - t.Fatalf("Failed to query inode: %v", err) - } - - if count != 1 { - t.Error("Dentry references non-existent inode") - } -} - -// FS-26: Every dentry MUST reference a valid parent inode -func TestSpec_FS26_DentryReferencesValidParent(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create nested structure - err := afs.FS.Mkdir(ctx, "/parent", 0o755) - if err != nil { - t.Fatalf("Failed to mkdir: %v", err) - } - - err = afs.FS.WriteFile(ctx, "/parent/child.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("Failed to write file: %v", err) - } - - // Get child's parent_ino - var parentIno int64 - err = afs.db.QueryRowContext(ctx, - "SELECT parent_ino FROM fs_dentry WHERE name = 'child.txt'").Scan(&parentIno) - if err != nil { - t.Fatalf("Failed to get dentry: %v", err) - } - - // Verify parent inode exists and is a directory - var mode int64 - err = afs.db.QueryRowContext(ctx, "SELECT mode FROM fs_inode WHERE ino = ?", parentIno).Scan(&mode) - if err != nil { - t.Fatalf("Parent inode doesn't exist: %v", err) - } - - if mode&S_IFMT != S_IFDIR { - t.Error("Parent inode is not a directory") - } -} - -// FS-27: No directory MAY contain duplicate names -func TestSpec_FS27_NoDuplicateNames(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create a file - err := afs.FS.WriteFile(ctx, "/unique.txt", []byte("first"), 0o644) - if err != nil { - t.Fatalf("Failed to create file: %v", err) - } - - // Try to create directory with same name - should fail - err = afs.FS.Mkdir(ctx, "/unique.txt", 0o755) - if err == nil { - t.Error("Should not allow duplicate name in directory") - } -} - -// FS-28: Directories MUST have mode with S_IFDIR bit set -func TestSpec_FS28_DirectoryMode(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.Mkdir(ctx, "/testdir", 0o755) - if err != nil { - t.Fatalf("Failed to mkdir: %v", err) - } - - stats, err := afs.FS.Stat(ctx, "/testdir") - if err != nil { - t.Fatalf("Failed to stat: %v", err) - } - - if stats.Mode&S_IFMT != S_IFDIR { - t.Errorf("Directory should have S_IFDIR, got mode %o", stats.Mode) - } -} - -// FS-29: Regular files MUST have mode with S_IFREG bit set -func TestSpec_FS29_RegularFileMode(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/test.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats, err := afs.FS.Stat(ctx, "/test.txt") - if err != nil { - t.Fatalf("Failed to stat: %v", err) - } - - if stats.Mode&S_IFMT != S_IFREG { - t.Errorf("File should have S_IFREG, got mode %o", stats.Mode) - } -} - -// FS-30: File size MUST match total size of all data chunks -func TestSpec_FS30_FileSizeMatchesContent(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - content := []byte("hello world - this is test content") - err := afs.FS.WriteFile(ctx, "/test.txt", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats, err := afs.FS.Stat(ctx, "/test.txt") - if err != nil { - t.Fatalf("Failed to stat: %v", err) - } - - if stats.Size != int64(len(content)) { - t.Errorf("Size should be %d, got %d", len(content), stats.Size) - } -} - -// FS-31: Every inode MUST have at least one dentry (except root) -func TestSpec_FS31_InodeHasDentry(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create a file - err := afs.FS.WriteFile(ctx, "/test.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("Failed to write file: %v", err) - } - - // Get all non-root inodes - collect them first to avoid nested query issue - rows, err := afs.db.QueryContext(ctx, "SELECT ino FROM fs_inode WHERE ino != 1") - if err != nil { - t.Fatalf("Failed to query inodes: %v", err) - } - - var inodes []int64 - for rows.Next() { - var ino int64 - if err := rows.Scan(&ino); err != nil { - rows.Close() - t.Fatalf("Failed to scan: %v", err) - } - inodes = append(inodes, ino) - } - rows.Close() - - // Now check each inode - for _, ino := range inodes { - var count int - err = afs.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM fs_dentry WHERE ino = ?", ino).Scan(&count) - if err != nil { - t.Fatalf("Failed to count dentries for inode %d: %v", ino, err) - } - - if count < 1 { - t.Errorf("Inode %d has no dentry entries", ino) - } - } -} - -// FS-32: Path resolution starts at root inode -func TestSpec_FS32_PathResolutionFromRoot(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.Mkdir(ctx, "/dir", 0o755) - if err != nil { - t.Fatalf("Failed to mkdir: %v", err) - } - - err = afs.FS.WriteFile(ctx, "/dir/file.txt", []byte("test"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - // Absolute path should resolve from root - _, err = afs.FS.Stat(ctx, "/dir/file.txt") - if err != nil { - t.Error("Failed to resolve absolute path from root") - } -} - -// FS-33: Path components are split by / with empty components filtered -func TestSpec_FS33_PathComponentsSplit(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create nested path - err := afs.FS.Mkdir(ctx, "/a", 0o755) - if err != nil { - t.Fatalf("Failed to mkdir /a: %v", err) - } - err = afs.FS.Mkdir(ctx, "/a/b", 0o755) - if err != nil { - t.Fatalf("Failed to mkdir /a/b: %v", err) - } - err = afs.FS.WriteFile(ctx, "/a/b/c.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("Failed to write file: %v", err) - } - - // Path with trailing slash should still work - stats, err := afs.FS.Stat(ctx, "/a/b/") - if err != nil { - t.Fatalf("Failed to stat /a/b/: %v", err) - } - if !stats.IsDir() { - t.Error("/a/b/ should be a directory") - } - - // Path with double slashes should work (empty components filtered) - _, err = afs.FS.Stat(ctx, "/a//b") - if err != nil { - t.Error("Path with double slashes should resolve correctly") - } -} - -// FS-34: Each component lookup uses parent_ino and name -func TestSpec_FS34_ComponentLookup(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create structure with same name at different levels - err := afs.FS.Mkdir(ctx, "/dir", 0o755) - if err != nil { - t.Fatalf("Failed to mkdir /dir: %v", err) - } - err = afs.FS.WriteFile(ctx, "/file.txt", []byte("root level"), 0o644) - if err != nil { - t.Fatalf("Failed to write /file.txt: %v", err) - } - err = afs.FS.WriteFile(ctx, "/dir/file.txt", []byte("nested level"), 0o644) - if err != nil { - t.Fatalf("Failed to write /dir/file.txt: %v", err) - } - - // Both should be accessible and distinct - stats1, err := afs.FS.Stat(ctx, "/file.txt") - if err != nil { - t.Fatalf("Failed to stat /file.txt: %v", err) - } - - stats2, err := afs.FS.Stat(ctx, "/dir/file.txt") - if err != nil { - t.Fatalf("Failed to stat /dir/file.txt: %v", err) - } - - if stats1.Ino == stats2.Ino { - t.Error("Same-named files in different directories should have different inodes") - } -} - -// FS-35: Missing component returns not found -func TestSpec_FS35_MissingComponentNotFound(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - _, err := afs.FS.Stat(ctx, "/nonexistent/path/file.txt") - if !IsNotExist(err) { - t.Error("Missing path component should return not found") - } -} - -// FS-36: Creating file resolves parent directory first -func TestSpec_FS36_CreateResolvesParent(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // WriteFile resolves parent directory (creating it if needed via MkdirAll) - // and then creates the file - err := afs.FS.WriteFile(ctx, "/auto_created/file.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Verify parent directory was created - stats, err := afs.FS.Stat(ctx, "/auto_created") - if err != nil { - t.Fatalf("Parent directory should exist: %v", err) - } - if !stats.IsDir() { - t.Error("Parent should be a directory") - } - - // Verify file exists - _, err = afs.FS.Stat(ctx, "/auto_created/file.txt") - if err != nil { - t.Error("File should exist after WriteFile") - } -} - -// FS-37: New inode is inserted with correct mode, uid, gid, timestamps -func TestSpec_FS37_InodeFields(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - beforeCreate := time.Now().Unix() - err := afs.FS.WriteFile(ctx, "/test.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - afterCreate := time.Now().Unix() - - stats, err := afs.FS.Stat(ctx, "/test.txt") - if err != nil { - t.Fatalf("Failed to stat: %v", err) - } - - // Check mode - if stats.Mode&0o777 != 0o644 { - t.Errorf("Expected mode 0644, got %o", stats.Mode&0o777) - } - if stats.Mode&S_IFMT != S_IFREG { - t.Error("Expected regular file type") - } - - // Check timestamps are reasonable - if stats.Ctime < beforeCreate || stats.Ctime > afterCreate { - t.Errorf("Ctime %d not in expected range [%d, %d]", stats.Ctime, beforeCreate, afterCreate) - } - if stats.Mtime < beforeCreate || stats.Mtime > afterCreate { - t.Errorf("Mtime %d not in expected range [%d, %d]", stats.Mtime, beforeCreate, afterCreate) - } -} - -// FS-38: Directory entry is created linking name to inode -func TestSpec_FS38_DentryCreated(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/linked.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - // Verify dentry exists - var dentryIno int64 - err = afs.db.QueryRowContext(ctx, - "SELECT ino FROM fs_dentry WHERE parent_ino = 1 AND name = 'linked.txt'").Scan(&dentryIno) - if err != nil { - t.Fatalf("Dentry not created: %v", err) - } - - // Verify it points to the correct inode - stats, _ := afs.FS.Stat(ctx, "/linked.txt") - if dentryIno != stats.Ino { - t.Errorf("Dentry ino %d doesn't match stat ino %d", dentryIno, stats.Ino) - } -} - -// FS-39: Link count is incremented on inode -func TestSpec_FS39_LinkCountIncrement(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/original.txt", []byte("test"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats1, _ := afs.FS.Stat(ctx, "/original.txt") - - // Create hard link - err = afs.FS.Link(ctx, "/original.txt", "/linked.txt") - if err != nil { - t.Fatalf("Failed to link: %v", err) - } - - stats2, _ := afs.FS.Stat(ctx, "/original.txt") - - if stats2.Nlink != stats1.Nlink+1 { - t.Errorf("Nlink should increment from %d to %d, got %d", stats1.Nlink, stats1.Nlink+1, stats2.Nlink) - } -} - -// FS-40: Data is split into chunks of chunk_size -func TestSpec_FS40_DataChunking(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create content larger than one chunk (default 4096) - content := make([]byte, 10000) // ~2.4 chunks - for i := range content { - content[i] = byte(i % 256) - } - - err := afs.FS.WriteFile(ctx, "/large.bin", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats, _ := afs.FS.Stat(ctx, "/large.bin") - - // Count chunks - var chunkCount int - err = afs.db.QueryRowContext(ctx, - "SELECT COUNT(*) FROM fs_data WHERE ino = ?", stats.Ino).Scan(&chunkCount) - if err != nil { - t.Fatalf("Failed to count chunks: %v", err) - } - - expectedChunks := (len(content) + 4095) / 4096 // Ceiling division - if chunkCount != expectedChunks { - t.Errorf("Expected %d chunks, got %d", expectedChunks, chunkCount) - } -} - -// FS-41: Inode size is updated to match content -func TestSpec_FS41_InodeSizeUpdated(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - content := []byte("hello world") - err := afs.FS.WriteFile(ctx, "/sized.txt", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats, _ := afs.FS.Stat(ctx, "/sized.txt") - if stats.Size != int64(len(content)) { - t.Errorf("Expected size %d, got %d", len(content), stats.Size) - } - - // Update with different content - newContent := []byte("new longer content here") - err = afs.FS.WriteFile(ctx, "/sized.txt", newContent, 0o644) - if err != nil { - t.Fatalf("Failed to rewrite: %v", err) - } - - stats, _ = afs.FS.Stat(ctx, "/sized.txt") - if stats.Size != int64(len(newContent)) { - t.Errorf("Expected updated size %d, got %d", len(newContent), stats.Size) - } -} - -// FS-42: Reading file resolves path to inode -func TestSpec_FS42_ReadResolvesPath(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.Mkdir(ctx, "/dir", 0o755) - if err != nil { - t.Fatalf("Failed to mkdir: %v", err) - } - - content := []byte("nested content") - err = afs.FS.WriteFile(ctx, "/dir/nested.txt", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - // Read via path - data, err := afs.FS.ReadFile(ctx, "/dir/nested.txt") - if err != nil { - t.Fatalf("Failed to read: %v", err) - } - - if string(data) != string(content) { - t.Errorf("Expected %q, got %q", string(content), string(data)) - } -} - -// FS-43: All chunks are fetched in order by chunk_index ASC -func TestSpec_FS43_ChunkOrder(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create content spanning multiple chunks - content := make([]byte, 10000) - for i := range content { - content[i] = byte(i % 256) - } - - err := afs.FS.WriteFile(ctx, "/ordered.bin", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - // Read back and verify order - data, err := afs.FS.ReadFile(ctx, "/ordered.bin") - if err != nil { - t.Fatalf("Failed to read: %v", err) - } - - for i := range data { - if data[i] != byte(i%256) { - t.Errorf("Byte %d: expected %d, got %d", i, byte(i%256), data[i]) - break - } - } -} - -// FS-44: Chunks are concatenated correctly -func TestSpec_FS44_ChunkConcatenation(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create known pattern across chunk boundaries - content := make([]byte, 8192+100) // 2 full chunks + partial - for i := range content { - content[i] = byte(i % 256) - } - - err := afs.FS.WriteFile(ctx, "/concat.bin", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - data, err := afs.FS.ReadFile(ctx, "/concat.bin") - if err != nil { - t.Fatalf("Failed to read: %v", err) - } - - if len(data) != len(content) { - t.Fatalf("Expected length %d, got %d", len(content), len(data)) - } - - // Verify chunk boundary (4096) - if data[4095] != content[4095] || data[4096] != content[4096] { - t.Error("Chunk boundary not concatenated correctly") - } -} - -// FS-45: Access time (atime) is updated on read -func TestSpec_FS45_AtimeUpdated(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/atime.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats1, _ := afs.FS.Stat(ctx, "/atime.txt") - initialAtime := stats1.Atime - - // Wait a moment - time.Sleep(1100 * time.Millisecond) - - // Read the file - _, err = afs.FS.ReadFile(ctx, "/atime.txt") - if err != nil { - t.Fatalf("Failed to read: %v", err) - } - - stats2, _ := afs.FS.Stat(ctx, "/atime.txt") - - // Atime should be updated (or at least not older) - if stats2.Atime < initialAtime { - t.Error("Atime should not decrease after read") - } -} - -// FS-46: start_chunk = offset / chunk_size -func TestSpec_FS46_StartChunkCalculation(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create file with 3 chunks - content := make([]byte, 12288) // 3 * 4096 - for i := range content { - content[i] = byte(i / 4096) // Each chunk has its index as byte value - } - - err := afs.FS.WriteFile(ctx, "/chunked.bin", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - // Read from middle of second chunk (offset 5000) - f, err := afs.FS.Open(ctx, "/chunked.bin", O_RDONLY) - if err != nil { - t.Fatalf("Failed to open: %v", err) - } - defer f.Close() - - buf := make([]byte, 10) - _, err = f.ReadAt(buf, 5000) - if err != nil { - t.Fatalf("Failed to ReadAt: %v", err) - } - - // Offset 5000 is in chunk 1 (5000/4096 = 1), should read value 1 - if buf[0] != 1 { - t.Errorf("Expected byte value 1 at offset 5000, got %d", buf[0]) - } -} - -// FS-47: end_chunk = (offset + length - 1) / chunk_size -func TestSpec_FS47_EndChunkCalculation(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create file spanning multiple chunks - content := make([]byte, 12288) - for i := range content { - content[i] = byte(i / 4096) - } - - err := afs.FS.WriteFile(ctx, "/endchunk.bin", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - // Read across chunk boundary - f, err := afs.FS.Open(ctx, "/endchunk.bin", O_RDONLY) - if err != nil { - t.Fatalf("Failed to open: %v", err) - } - defer f.Close() - - // Read from end of chunk 0 into chunk 1 - buf := make([]byte, 200) - _, err = f.ReadAt(buf, 4000) // 4000 to 4200 spans chunks 0 and 1 - if err != nil { - t.Fatalf("Failed to ReadAt: %v", err) - } - - // First 96 bytes should be from chunk 0 - if buf[0] != 0 { - t.Errorf("Expected chunk 0 data at start, got %d", buf[0]) - } - // Bytes after 96 should be from chunk 1 - if buf[100] != 1 { - t.Errorf("Expected chunk 1 data at position 100, got %d", buf[100]) - } -} - -// FS-48: Only required chunks are fetched -func TestSpec_FS48_OnlyRequiredChunks(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create 5-chunk file - content := make([]byte, 20480) // 5 * 4096 - for i := range content { - content[i] = byte(i / 4096) - } - - err := afs.FS.WriteFile(ctx, "/selective.bin", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - f, err := afs.FS.Open(ctx, "/selective.bin", O_RDONLY) - if err != nil { - t.Fatalf("Failed to open: %v", err) - } - defer f.Close() - - // Read only from chunk 2 (offset 8192-12287) - buf := make([]byte, 100) - _, err = f.ReadAt(buf, 8192) - if err != nil { - t.Fatalf("Failed to ReadAt: %v", err) - } - - // Should contain chunk 2 data - if buf[0] != 2 { - t.Errorf("Expected chunk 2 data, got %d", buf[0]) - } -} - -// FS-49: offset_in_first_chunk = offset % chunk_size -func TestSpec_FS49_OffsetInFirstChunk(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - content := make([]byte, 8192) - for i := range content { - content[i] = byte(i % 256) - } - - err := afs.FS.WriteFile(ctx, "/offset.bin", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - f, err := afs.FS.Open(ctx, "/offset.bin", O_RDONLY) - if err != nil { - t.Fatalf("Failed to open: %v", err) - } - defer f.Close() - - // Read from offset 4500 (offset in chunk = 4500 % 4096 = 404) - buf := make([]byte, 10) - _, err = f.ReadAt(buf, 4500) - if err != nil { - t.Fatalf("Failed to ReadAt: %v", err) - } - - // Should get byte at position 4500 - expected := byte(4500 % 256) - if buf[0] != expected { - t.Errorf("Expected byte %d at offset 4500, got %d", expected, buf[0]) - } -} - -// FS-50: Correct byte range is extracted -func TestSpec_FS50_CorrectByteRange(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - content := make([]byte, 10000) - for i := range content { - content[i] = byte(i % 256) - } - - err := afs.FS.WriteFile(ctx, "/range.bin", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - f, err := afs.FS.Open(ctx, "/range.bin", O_RDONLY) - if err != nil { - t.Fatalf("Failed to open: %v", err) - } - defer f.Close() - - // Read specific range - buf := make([]byte, 500) - n, err := f.ReadAt(buf, 1000) - if err != nil { - t.Fatalf("Failed to ReadAt: %v", err) - } - - if n != 500 { - t.Errorf("Expected to read 500 bytes, got %d", n) - } - - // Verify all bytes match original - for i := 0; i < 500; i++ { - expected := byte((1000 + i) % 256) - if buf[i] != expected { - t.Errorf("Byte %d: expected %d, got %d", i, expected, buf[i]) - break - } - } -} - -// FS-51: Readdir returns names from fs_dentry for parent_ino -func TestSpec_FS51_ReaddirReturnsNames(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.Mkdir(ctx, "/mydir", 0o755) - if err != nil { - t.Fatalf("Failed to mkdir: %v", err) - } - - _ = afs.FS.WriteFile(ctx, "/mydir/a.txt", []byte("a"), 0o644) - _ = afs.FS.WriteFile(ctx, "/mydir/b.txt", []byte("b"), 0o644) - _ = afs.FS.WriteFile(ctx, "/mydir/c.txt", []byte("c"), 0o644) - - entries, err := afs.FS.Readdir(ctx, "/mydir") - if err != nil { - t.Fatalf("Failed to readdir: %v", err) - } - - if len(entries) != 3 { - t.Errorf("Expected 3 entries, got %d", len(entries)) - } -} - -// FS-52: Readdir results are ordered by name ASC -func TestSpec_FS52_ReaddirOrdered(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.Mkdir(ctx, "/ordered", 0o755) - if err != nil { - t.Fatalf("Failed to mkdir: %v", err) - } - - // Create in non-alphabetical order - _ = afs.FS.WriteFile(ctx, "/ordered/zebra.txt", []byte("z"), 0o644) - _ = afs.FS.WriteFile(ctx, "/ordered/apple.txt", []byte("a"), 0o644) - _ = afs.FS.WriteFile(ctx, "/ordered/mango.txt", []byte("m"), 0o644) - - entries, err := afs.FS.Readdir(ctx, "/ordered") - if err != nil { - t.Fatalf("Failed to readdir: %v", err) - } - - expected := []string{"apple.txt", "mango.txt", "zebra.txt"} - for i, name := range expected { - if entries[i] != name { - t.Errorf("Entry %d: expected %q, got %q", i, name, entries[i]) - } - } -} - -// FS-53: Deleting file removes directory entry -func TestSpec_FS53_UnlinkRemovesDentry(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/to_delete.txt", []byte("delete me"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - err = afs.FS.Unlink(ctx, "/to_delete.txt") - if err != nil { - t.Fatalf("Failed to unlink: %v", err) - } - - _, err = afs.FS.Stat(ctx, "/to_delete.txt") - if !IsNotExist(err) { - t.Error("File should not exist after unlink") - } -} - -// FS-54: Link count is decremented on delete -func TestSpec_FS54_LinkCountDecrement(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/original.txt", []byte("test"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - _ = afs.FS.Link(ctx, "/original.txt", "/link1.txt") - _ = afs.FS.Link(ctx, "/original.txt", "/link2.txt") - - stats1, _ := afs.FS.Stat(ctx, "/original.txt") - - // Remove one link - _ = afs.FS.Unlink(ctx, "/link1.txt") - - stats2, _ := afs.FS.Stat(ctx, "/original.txt") - - if stats2.Nlink != stats1.Nlink-1 { - t.Errorf("Nlink should decrement from %d to %d", stats1.Nlink, stats1.Nlink-1) - } -} - -// FS-55: When nlink reaches 0, inode is deleted -func TestSpec_FS55_InodeDeletedWhenNlinkZero(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/to_remove.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats, _ := afs.FS.Stat(ctx, "/to_remove.txt") - ino := stats.Ino - - // Delete the file - err = afs.FS.Unlink(ctx, "/to_remove.txt") - if err != nil { - t.Fatalf("Failed to unlink: %v", err) - } - - // Inode should be deleted - var count int - err = afs.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM fs_inode WHERE ino = ?", ino).Scan(&count) - if err != nil { - t.Fatalf("Failed to query: %v", err) - } - - if count != 0 { - t.Error("Inode should be deleted when nlink reaches 0") - } -} - -// FS-56: When nlink reaches 0, data chunks are deleted -func TestSpec_FS56_DataDeletedWhenNlinkZero(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - content := make([]byte, 5000) // More than one chunk - err := afs.FS.WriteFile(ctx, "/data_remove.txt", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats, _ := afs.FS.Stat(ctx, "/data_remove.txt") - ino := stats.Ino - - // Verify chunks exist - var chunkCount int - afs.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM fs_data WHERE ino = ?", ino).Scan(&chunkCount) - if chunkCount == 0 { - t.Fatal("Expected chunks before delete") - } - - // Delete the file - err = afs.FS.Unlink(ctx, "/data_remove.txt") - if err != nil { - t.Fatalf("Failed to unlink: %v", err) - } - - // Data chunks should be deleted - err = afs.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM fs_data WHERE ino = ?", ino).Scan(&chunkCount) - if err != nil { - t.Fatalf("Failed to query: %v", err) - } - - if chunkCount != 0 { - t.Error("Data chunks should be deleted when nlink reaches 0") - } -} - -// FS-57: Hard link creates new dentry pointing to same inode -func TestSpec_FS57_HardLinkSameInode(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/original.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - err = afs.FS.Link(ctx, "/original.txt", "/hardlink.txt") - if err != nil { - t.Fatalf("Failed to link: %v", err) - } - - stats1, _ := afs.FS.Stat(ctx, "/original.txt") - stats2, _ := afs.FS.Stat(ctx, "/hardlink.txt") - - if stats1.Ino != stats2.Ino { - t.Errorf("Hard link should have same inode: original=%d, link=%d", stats1.Ino, stats2.Ino) - } -} - -// FS-58: Link count is incremented on target inode (via Link operation) -func TestSpec_FS58_LinkIncrementOnTarget(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/target.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats1, _ := afs.FS.Stat(ctx, "/target.txt") - initialNlink := stats1.Nlink - - // Create hard link - err = afs.FS.Link(ctx, "/target.txt", "/hardlink.txt") - if err != nil { - t.Fatalf("Failed to link: %v", err) - } - - stats2, _ := afs.FS.Stat(ctx, "/target.txt") - - if stats2.Nlink != initialNlink+1 { - t.Errorf("Expected nlink to increment from %d to %d, got %d", - initialNlink, initialNlink+1, stats2.Nlink) - } -} - -// FS-59: Multiple dentries can point to same inode -func TestSpec_FS59_MultipleDentries(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/original.txt", []byte("shared content"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - // Create multiple hard links - _ = afs.FS.Link(ctx, "/original.txt", "/link1.txt") - _ = afs.FS.Link(ctx, "/original.txt", "/link2.txt") - _ = afs.FS.Link(ctx, "/original.txt", "/link3.txt") - - stats, _ := afs.FS.Stat(ctx, "/original.txt") - - // Count dentries pointing to this inode - var dentryCount int - err = afs.db.QueryRowContext(ctx, - "SELECT COUNT(*) FROM fs_dentry WHERE ino = ?", stats.Ino).Scan(&dentryCount) - if err != nil { - t.Fatalf("Failed to count dentries: %v", err) - } - - if dentryCount != 4 { - t.Errorf("Expected 4 dentries (original + 3 links), got %d", dentryCount) - } - - // Verify nlink matches - if stats.Nlink != int64(dentryCount) { - t.Errorf("Nlink %d should match dentry count %d", stats.Nlink, dentryCount) - } -} - -// FS-60: Stat returns all inode fields -func TestSpec_FS60_StatReturnsAllFields(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/test.txt", []byte("test content"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats, err := afs.FS.Stat(ctx, "/test.txt") - if err != nil { - t.Fatalf("Failed to stat: %v", err) - } - - // Verify all fields are populated - if stats.Ino == 0 { - t.Error("Ino should be set") - } - if stats.Mode == 0 { - t.Error("Mode should be set") - } - if stats.Size != 12 { - t.Errorf("Size should be 12, got %d", stats.Size) - } - if stats.Atime == 0 { - t.Error("Atime should be set") - } - if stats.Mtime == 0 { - t.Error("Mtime should be set") - } - if stats.Ctime == 0 { - t.Error("Ctime should be set") - } -} - -// FS-61: Stat returns nanosecond fields (atime_nsec, mtime_nsec, ctime_nsec) -func TestSpec_FS61_StatNanoseconds(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/nsec.txt", []byte("content"), 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats, err := afs.FS.Stat(ctx, "/nsec.txt") - if err != nil { - t.Fatalf("Failed to stat: %v", err) - } - - // Nanosecond fields should be in valid range [0, 999999999] - if stats.AtimeNsec < 0 || stats.AtimeNsec > 999999999 { - t.Errorf("AtimeNsec %d out of range", stats.AtimeNsec) - } - if stats.MtimeNsec < 0 || stats.MtimeNsec > 999999999 { - t.Errorf("MtimeNsec %d out of range", stats.MtimeNsec) - } - if stats.CtimeNsec < 0 || stats.CtimeNsec > 999999999 { - t.Errorf("CtimeNsec %d out of range", stats.CtimeNsec) - } -} - -// FS-62: Directories MUST NOT have data chunks -func TestSpec_FS62_DirectoriesNoDataChunks(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.Mkdir(ctx, "/testdir", 0o755) - if err != nil { - t.Fatalf("Failed to mkdir: %v", err) - } - - stats, _ := afs.FS.Stat(ctx, "/testdir") - - // Query for data chunks - var count int - err = afs.db.QueryRow("SELECT COUNT(*) FROM fs_data WHERE ino = ?", stats.Ino).Scan(&count) - if err != nil { - t.Fatalf("Failed to query: %v", err) - } - - if count != 0 { - t.Error("Directory should not have data chunks") - } -} - -// FS-63: All chunks except last MUST be exactly chunk_size bytes -func TestSpec_FS63_ChunkSizeExact(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create file with 2.5 chunks - content := make([]byte, 10240) // 4096 + 4096 + 2048 - err := afs.FS.WriteFile(ctx, "/chunks.bin", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats, _ := afs.FS.Stat(ctx, "/chunks.bin") - - // Check all chunks except last - rows, err := afs.db.QueryContext(ctx, - "SELECT chunk_index, LENGTH(data) FROM fs_data WHERE ino = ? ORDER BY chunk_index", - stats.Ino) - if err != nil { - t.Fatalf("Failed to query chunks: %v", err) - } - defer rows.Close() - - var chunks []struct { - index int - size int - } - for rows.Next() { - var idx, size int - rows.Scan(&idx, &size) - chunks = append(chunks, struct { - index int - size int - }{idx, size}) - } - - // All except last should be 4096 - for i := 0; i < len(chunks)-1; i++ { - if chunks[i].size != 4096 { - t.Errorf("Chunk %d should be 4096 bytes, got %d", chunks[i].index, chunks[i].size) - } - } -} - -// FS-64: Last chunk MAY be smaller than chunk_size -func TestSpec_FS64_LastChunkSmaller(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create file with partial last chunk - content := make([]byte, 5000) // 4096 + 904 - err := afs.FS.WriteFile(ctx, "/partial.bin", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats, _ := afs.FS.Stat(ctx, "/partial.bin") - - // Get last chunk size - var lastChunkSize int - err = afs.db.QueryRowContext(ctx, - "SELECT LENGTH(data) FROM fs_data WHERE ino = ? ORDER BY chunk_index DESC LIMIT 1", - stats.Ino).Scan(&lastChunkSize) - if err != nil { - t.Fatalf("Failed to get last chunk: %v", err) - } - - expectedLastSize := 5000 - 4096 // 904 - if lastChunkSize != expectedLastSize { - t.Errorf("Last chunk should be %d bytes, got %d", expectedLastSize, lastChunkSize) - } -} - -// FS-65: Byte offset = chunk_index * chunk_size -func TestSpec_FS65_ByteOffset(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create file with specific pattern at chunk boundaries - content := make([]byte, 12288) // 3 chunks - content[0] = 0xAA // Start of chunk 0 - content[4096] = 0xBB // Start of chunk 1 - content[8192] = 0xCC // Start of chunk 2 - - err := afs.FS.WriteFile(ctx, "/offsets.bin", content, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - // Read back and verify offsets - data, err := afs.FS.ReadFile(ctx, "/offsets.bin") - if err != nil { - t.Fatalf("Failed to read: %v", err) - } - - if data[0] != 0xAA { - t.Errorf("Chunk 0 offset wrong: expected 0xAA at 0, got %02X", data[0]) - } - if data[4096] != 0xBB { - t.Errorf("Chunk 1 offset wrong: expected 0xBB at 4096, got %02X", data[4096]) - } - if data[8192] != 0xCC { - t.Errorf("Chunk 2 offset wrong: expected 0xCC at 8192, got %02X", data[8192]) - } -} - -// FS-66: Empty files have inode but no data chunks -func TestSpec_FS66_EmptyFileNoChunks(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.WriteFile(ctx, "/empty.txt", []byte{}, 0o644) - if err != nil { - t.Fatalf("Failed to write: %v", err) - } - - stats, _ := afs.FS.Stat(ctx, "/empty.txt") - - // Should have inode - if stats.Ino == 0 { - t.Error("Empty file should have inode") - } - - // Should have no data chunks - var count int - err = afs.db.QueryRow("SELECT COUNT(*) FROM fs_data WHERE ino = ?", stats.Ino).Scan(&count) - if err != nil { - t.Fatalf("Failed to query: %v", err) - } - - if count != 0 { - t.Error("Empty file should not have data chunks") - } -} - -// FS-67: Symlink inode is stored in fs_symlink table -func TestSpec_FS67_SymlinkStored(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.Symlink(ctx, "/target/path", "/mylink") - if err != nil { - t.Fatalf("Failed to symlink: %v", err) - } - - stats, err := afs.FS.Lstat(ctx, "/mylink") - if err != nil { - t.Fatalf("Failed to lstat: %v", err) - } - - // Verify entry in fs_symlink - var target string - err = afs.db.QueryRow("SELECT target FROM fs_symlink WHERE ino = ?", stats.Ino).Scan(&target) - if err != nil { - t.Fatalf("Symlink not in fs_symlink table: %v", err) - } - - if target != "/target/path" { - t.Errorf("Expected target '/target/path', got %q", target) - } -} - -// FS-68: Symlink target can be absolute or relative path -func TestSpec_FS68_SymlinkTargetPaths(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Create absolute symlink - err := afs.FS.Symlink(ctx, "/absolute/path/target", "/abs_link") - if err != nil { - t.Fatalf("Failed to create absolute symlink: %v", err) - } - - // Create relative symlink - err = afs.FS.Symlink(ctx, "../relative/path", "/rel_link") - if err != nil { - t.Fatalf("Failed to create relative symlink: %v", err) - } - - // Verify targets are stored correctly - target1, err := afs.FS.Readlink(ctx, "/abs_link") - if err != nil { - t.Fatalf("Failed to readlink absolute: %v", err) - } - if target1 != "/absolute/path/target" { - t.Errorf("Absolute target mismatch: got %q", target1) - } - - target2, err := afs.FS.Readlink(ctx, "/rel_link") - if err != nil { - t.Fatalf("Failed to readlink relative: %v", err) - } - if target2 != "../relative/path" { - t.Errorf("Relative target mismatch: got %q", target2) - } -} - -// FS-69: Symlink has mode with S_IFLNK bit set -func TestSpec_FS69_SymlinkMode(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.FS.Symlink(ctx, "/target", "/symlink") - if err != nil { - t.Fatalf("Failed to symlink: %v", err) - } - - stats, err := afs.FS.Lstat(ctx, "/symlink") - if err != nil { - t.Fatalf("Failed to lstat: %v", err) - } - - if stats.Mode&S_IFMT != S_IFLNK { - t.Errorf("Symlink should have S_IFLNK, got mode %o", stats.Mode) - } -} - -// ============================================================================= -// Overlay Filesystem (OV-1 to OV-26) -// ============================================================================= - -// OV-1: Table fs_whiteout has path TEXT PRIMARY KEY -func TestSpec_OV1_FsWhiteoutTable(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !tableExists(t, afs.db, "fs_whiteout") { - t.Fatal("Table fs_whiteout does not exist") - } - if !columnExists(t, afs.db, "fs_whiteout", "path") { - t.Error("Column 'path' missing") - } -} - -// OV-2: Table fs_whiteout has parent_path TEXT NOT NULL -func TestSpec_OV2_FsWhiteoutParentPath(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !columnExists(t, afs.db, "fs_whiteout", "parent_path") { - t.Error("Column 'parent_path' missing") - } -} - -// OV-3: Table fs_whiteout has created_at INTEGER NOT NULL -func TestSpec_OV3_FsWhiteoutCreatedAt(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !columnExists(t, afs.db, "fs_whiteout", "created_at") { - t.Error("Column 'created_at' missing") - } -} - -// OV-4: Index idx_fs_whiteout_parent exists -func TestSpec_OV4_FsWhiteoutIndex(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !indexExists(t, afs.db, "idx_fs_whiteout_parent") { - t.Error("Index idx_fs_whiteout_parent does not exist") - } -} - -// OV-5: Table fs_origin has delta_ino INTEGER PRIMARY KEY -func TestSpec_OV5_FsOriginTable(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !tableExists(t, afs.db, "fs_origin") { - t.Fatal("Table fs_origin does not exist") - } - if !columnExists(t, afs.db, "fs_origin", "delta_ino") { - t.Error("Column 'delta_ino' missing") - } -} - -// OV-6: Table fs_origin has base_ino INTEGER NOT NULL -func TestSpec_OV6_FsOriginBaseIno(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !columnExists(t, afs.db, "fs_origin", "base_ino") { - t.Error("Column 'base_ino' missing") - } -} - -// OV-7: Create whiteout inserts path with parent_path and created_at -func TestSpec_OV7_CreateWhiteout(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - base.addFile(1, "to_delete.txt", 100, []byte("content"), 0o644) - - beforeDelete := time.Now().Unix() - err := ofs.Unlink(ctx, "/to_delete.txt") - if err != nil { - t.Fatalf("Unlink failed: %v", err) - } - afterDelete := time.Now().Unix() - - // Verify whiteout record - var path, parentPath string - var createdAt int64 - err = afs.db.QueryRowContext(ctx, - "SELECT path, parent_path, created_at FROM fs_whiteout WHERE path = '/to_delete.txt'"). - Scan(&path, &parentPath, &createdAt) - if err != nil { - t.Fatalf("Whiteout not found: %v", err) - } - - if parentPath != "/" { - t.Errorf("Expected parent_path '/', got %q", parentPath) - } - if createdAt < beforeDelete || createdAt > afterDelete { - t.Errorf("created_at %d not in expected range", createdAt) - } -} - -// OV-8: Check whiteout returns true for existing whiteout path -func TestSpec_OV8_CheckWhiteout(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - base.addFile(1, "check_wo.txt", 100, []byte("content"), 0o644) - - // Before delete - no whiteout - if ofs.isWhiteout("/check_wo.txt") { - t.Error("Should not be whiteout before delete") - } - - // Delete creates whiteout - _ = ofs.Unlink(ctx, "/check_wo.txt") - - // After delete - should be whiteout - if !ofs.isWhiteout("/check_wo.txt") { - t.Error("Should be whiteout after delete") - } -} - -// OV-9: Remove whiteout deletes the path entry -func TestSpec_OV9_RemoveWhiteout(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - base.addFile(1, "recreate.txt", 100, []byte("original"), 0o644) - - // Delete to create whiteout - _ = ofs.Unlink(ctx, "/recreate.txt") - if !ofs.isWhiteout("/recreate.txt") { - t.Fatal("Whiteout should exist") - } - - // Create new file at same path (should remove whiteout) - err := ofs.WriteFile(ctx, "/recreate.txt", []byte("new"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Whiteout should be removed - if ofs.isWhiteout("/recreate.txt") { - t.Error("Whiteout should be removed after creating new file") - } - - // Verify in database - var count int - afs.db.QueryRowContext(ctx, - "SELECT COUNT(*) FROM fs_whiteout WHERE path = '/recreate.txt'").Scan(&count) - if count != 0 { - t.Error("Whiteout record should be deleted from database") - } -} - -// OV-10: List child whiteouts returns paths with matching parent_path -func TestSpec_OV10_ListChildWhiteouts(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Create files in base at different levels - base.addDir(1, "subdir", 50, 0o755) - base.addFile(1, "root1.txt", 100, []byte("r1"), 0o644) - base.addFile(1, "root2.txt", 101, []byte("r2"), 0o644) - base.addFile(50, "child1.txt", 102, []byte("c1"), 0o644) - base.addFile(50, "child2.txt", 103, []byte("c2"), 0o644) - - // Delete files at root level - _ = ofs.Unlink(ctx, "/root1.txt") - _ = ofs.Unlink(ctx, "/root2.txt") - _ = ofs.Unlink(ctx, "/subdir/child1.txt") - - // Query whiteouts with parent_path = "/" - rows, err := afs.db.QueryContext(ctx, - "SELECT path FROM fs_whiteout WHERE parent_path = '/'") - if err != nil { - t.Fatalf("Query failed: %v", err) - } - defer rows.Close() - - var rootWhiteouts []string - for rows.Next() { - var p string - rows.Scan(&p) - rootWhiteouts = append(rootWhiteouts, p) - } - - if len(rootWhiteouts) != 2 { - t.Errorf("Expected 2 root-level whiteouts, got %d", len(rootWhiteouts)) - } -} - -// OV-11: Lookup returns delta entry if path exists in delta -func TestSpec_OV11_LookupReturnsDelta(t *testing.T) { - ctx := context.Background() - ofs, _, afs := setupOverlayTest(t) - defer afs.Close() - - // Create file in delta only - err := ofs.WriteFile(ctx, "/delta_only.txt", []byte("delta content"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - stats, err := ofs.LookupPath(ctx, "/delta_only.txt") - if err != nil { - t.Fatalf("LookupPath failed: %v", err) - } - - if stats == nil { - t.Error("Should find delta-only file") - } -} - -// OV-12: Lookup returns "not found" if path has whiteout -func TestSpec_OV12_LookupWhiteoutNotFound(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - base.addFile(1, "whited_out.txt", 100, []byte("base"), 0o644) - - // Create whiteout - _ = ofs.Unlink(ctx, "/whited_out.txt") - - // Lookup should return not found - _, err := ofs.LookupPath(ctx, "/whited_out.txt") - if !IsNotExist(err) { - t.Error("Lookup should return not found for whiteout path") - } -} - -// OV-13: Lookup returns base entry if path exists only in base -func TestSpec_OV13_LookupReturnsBase(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - base.addFile(1, "base_only.txt", 100, []byte("base content"), 0o644) - - stats, err := ofs.LookupPath(ctx, "/base_only.txt") - if err != nil { - t.Fatalf("LookupPath failed: %v", err) - } - - if stats == nil { - t.Error("Should find base-only file") - } -} - -// OV-14: Lookup returns "not found" if path doesn't exist in either layer -func TestSpec_OV14_LookupNonexistent(t *testing.T) { - ctx := context.Background() - ofs, _, afs := setupOverlayTest(t) - defer afs.Close() - - _, err := ofs.LookupPath(ctx, "/nonexistent.txt") - if !IsNotExist(err) { - t.Error("Lookup should return not found for nonexistent path") - } -} - -// OV-15: Store origin mapping inserts delta_ino → base_ino -func TestSpec_OV15_StoreOrigin(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - baseIno := int64(100) - base.addFile(1, "origin_test.txt", baseIno, []byte("content"), 0o644) - - // Trigger copy-up - err := ofs.Chmod(ctx, "/origin_test.txt", 0o600) - if err != nil { - t.Fatalf("Chmod failed: %v", err) - } - - // Verify origin mapping - var deltaIno, storedBaseIno int64 - err = afs.db.QueryRowContext(ctx, - "SELECT delta_ino, base_ino FROM fs_origin WHERE base_ino = ?", baseIno). - Scan(&deltaIno, &storedBaseIno) - if err != nil { - t.Fatalf("Origin mapping not found: %v", err) - } - - if storedBaseIno != baseIno { - t.Errorf("Expected base_ino %d, got %d", baseIno, storedBaseIno) - } - if deltaIno == 0 { - t.Error("delta_ino should be non-zero") - } -} - -// OV-16: Get origin returns base_ino for delta_ino if mapping exists -func TestSpec_OV16_GetOrigin(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - baseIno := int64(200) - base.addFile(1, "get_origin.txt", baseIno, []byte("content"), 0o644) - - // Trigger copy-up - _ = ofs.Chmod(ctx, "/get_origin.txt", 0o600) - - // Get the delta inode - var deltaIno int64 - err := afs.db.QueryRowContext(ctx, - "SELECT delta_ino FROM fs_origin WHERE base_ino = ?", baseIno).Scan(&deltaIno) - if err != nil { - t.Fatalf("Failed to get delta_ino: %v", err) - } - - // Query origin by delta_ino - var retrievedBaseIno int64 - err = afs.db.QueryRowContext(ctx, - "SELECT base_ino FROM fs_origin WHERE delta_ino = ?", deltaIno).Scan(&retrievedBaseIno) - if err != nil { - t.Fatalf("Failed to get origin: %v", err) - } - - if retrievedBaseIno != baseIno { - t.Errorf("Expected base_ino %d, got %d", baseIno, retrievedBaseIno) - } -} - -// OV-17: Origin mapping uses INSERT OR REPLACE semantics -func TestSpec_OV17_OriginReplace(t *testing.T) { - ctx := context.Background() - _, _, afs := setupOverlayTest(t) - defer afs.Close() - - // Insert origin mapping directly - _, err := afs.db.ExecContext(ctx, - "INSERT INTO fs_origin (delta_ino, base_ino) VALUES (1000, 500)") - if err != nil { - t.Fatalf("First insert failed: %v", err) - } - - // Replace with different base_ino - _, err = afs.db.ExecContext(ctx, - "INSERT OR REPLACE INTO fs_origin (delta_ino, base_ino) VALUES (1000, 600)") - if err != nil { - t.Fatalf("Replace failed: %v", err) - } - - // Verify only one mapping exists with updated value - var count int - var baseIno int64 - afs.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM fs_origin WHERE delta_ino = 1000").Scan(&count) - afs.db.QueryRowContext(ctx, "SELECT base_ino FROM fs_origin WHERE delta_ino = 1000").Scan(&baseIno) - - if count != 1 { - t.Errorf("Expected 1 mapping, got %d", count) - } - if baseIno != 600 { - t.Errorf("Expected base_ino 600, got %d", baseIno) - } -} - -// OV-18: Whiteout MUST be removed when new file created at that path -func TestSpec_OV18_WhiteoutRemovedOnCreate(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add file to base - base.addFile(1, "test.txt", 100, []byte("base content"), 0o644) - - // Delete it (creates whiteout) - err := ofs.Unlink(ctx, "/test.txt") - if err != nil { - t.Fatalf("Unlink failed: %v", err) - } - - // Verify whiteout exists - if !ofs.isWhiteout("/test.txt") { - t.Fatal("Whiteout should exist after delete") - } - - // Create new file at same path - err = ofs.WriteFile(ctx, "/test.txt", []byte("new content"), 0o644) - if err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - // Whiteout should be removed - if ofs.isWhiteout("/test.txt") { - t.Error("Whiteout should be removed after creating new file") - } -} - -// OV-19: Whiteout MUST be created when deleting file from base layer -func TestSpec_OV19_WhiteoutCreatedOnDelete(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add file to base - base.addFile(1, "base_only.txt", 100, []byte("base"), 0o644) - - // Delete it - err := ofs.Unlink(ctx, "/base_only.txt") - if err != nil { - t.Fatalf("Unlink failed: %v", err) - } - - // Whiteout should be created - if !ofs.isWhiteout("/base_only.txt") { - t.Error("Whiteout should be created when deleting base file") - } -} - -// OV-20: parent_path MUST be correctly derived from path -func TestSpec_OV20_ParentPathDerived(t *testing.T) { - testCases := []struct { - path string - parentPath string - }{ - {"/foo", "/"}, - {"/foo/bar", "/foo"}, - {"/foo/bar/baz", "/foo/bar"}, - {"/a", "/"}, - } - - for _, tc := range testCases { - result := parentPath(tc.path) - if result != tc.parentPath { - t.Errorf("parentPath(%q) = %q, expected %q", tc.path, result, tc.parentPath) - } - } -} - -// OV-21: Whiteouts only affect overlay lookups, not base filesystem -func TestSpec_OV21_WhiteoutsOverlayOnly(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add file to base - base.addFile(1, "base_file.txt", 100, []byte("base content"), 0o644) - - // Delete via overlay (creates whiteout) - err := ofs.Unlink(ctx, "/base_file.txt") - if err != nil { - t.Fatalf("Unlink failed: %v", err) - } - - // File should not be visible via overlay - _, err = ofs.LookupPath(ctx, "/base_file.txt") - if !IsNotExist(err) { - t.Error("File should not be visible via overlay after whiteout") - } - - // But base filesystem should still have it - _, err = base.Lookup(ctx, 1, "base_file.txt") - if err != nil { - t.Error("Base filesystem should still have the file") - } -} - -// OV-22: Origin mapping MUST be stored when copying from base to delta -func TestSpec_OV22_OriginMappingStored(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - // Add file to base - base.addFile(1, "to_copy.txt", 100, []byte("original"), 0o644) - - // Modify file (triggers copy-up) - err := ofs.Chmod(ctx, "/to_copy.txt", 0o600) - if err != nil { - t.Fatalf("Chmod failed: %v", err) - } - - // Verify origin mapping exists - var count int - err = afs.db.QueryRow("SELECT COUNT(*) FROM fs_origin WHERE base_ino = 100").Scan(&count) - if err != nil { - t.Fatalf("Query failed: %v", err) - } - - if count == 0 { - t.Error("Origin mapping should be stored after copy-up") - } -} - -// OV-23: Base inode MUST be returned when stat'ing delta file with origin -func TestSpec_OV23_BaseInodeReturned(t *testing.T) { - ctx := context.Background() - ofs, base, afs := setupOverlayTest(t) - defer afs.Close() - - baseIno := int64(100) - base.addFile(1, "origin_test.txt", baseIno, []byte("original"), 0o644) - - // Lookup before copy-up to get overlay inode - statsBefore, err := ofs.LookupPath(ctx, "/origin_test.txt") - if err != nil { - t.Fatalf("LookupPath failed: %v", err) - } - inoBefore := statsBefore.Ino - - // Modify file (triggers copy-up with origin mapping) - err = ofs.Chmod(ctx, "/origin_test.txt", 0o600) - if err != nil { - t.Fatalf("Chmod failed: %v", err) - } - - // Lookup after copy-up - statsAfter, err := ofs.LookupPath(ctx, "/origin_test.txt") - if err != nil { - t.Fatalf("LookupPath after copy-up failed: %v", err) - } - - // Inode should be stable (same overlay inode) - if statsAfter.Ino != inoBefore { - t.Errorf("Inode should be stable after copy-up: before=%d, after=%d", inoBefore, statsAfter.Ino) - } - - // Mode should reflect the change (read from delta) - if statsAfter.Mode&0o777 != 0o600 { - t.Errorf("Mode should be 0600, got %o", statsAfter.Mode&0o777) - } -} - -// OV-24: Root directory has parent_path "/" -func TestSpec_OV24_RootParentPath(t *testing.T) { - result := parentPath("/") - if result != "/" { - t.Errorf("parentPath('/') = %q, expected '/'", result) - } -} - -// OV-25: Path /foo/bar has parent_path /foo -func TestSpec_OV25_NestedParentPath(t *testing.T) { - result := parentPath("/foo/bar") - if result != "/foo" { - t.Errorf("parentPath('/foo/bar') = %q, expected '/foo'", result) - } -} - -// OV-26: Path /foo has parent_path / -func TestSpec_OV26_TopLevelParentPath(t *testing.T) { - result := parentPath("/foo") - if result != "/" { - t.Errorf("parentPath('/foo') = %q, expected '/'", result) - } -} - -// ============================================================================= -// Key-Value Store (KV-1 to KV-15) -// ============================================================================= - -// KV-1: Table kv_store has key TEXT PRIMARY KEY -func TestSpec_KV1_KvStoreTable(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !tableExists(t, afs.db, "kv_store") { - t.Fatal("Table kv_store does not exist") - } - if !columnExists(t, afs.db, "kv_store", "key") { - t.Error("Column 'key' missing") - } -} - -// KV-2: Table kv_store has value TEXT NOT NULL -func TestSpec_KV2_KvStoreValue(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !columnExists(t, afs.db, "kv_store", "value") { - t.Error("Column 'value' missing") - } -} - -// KV-3: Table kv_store has created_at -func TestSpec_KV3_KvStoreCreatedAt(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !columnExists(t, afs.db, "kv_store", "created_at") { - t.Error("Column 'created_at' missing") - } -} - -// KV-4: Table kv_store has updated_at -func TestSpec_KV4_KvStoreUpdatedAt(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !columnExists(t, afs.db, "kv_store", "updated_at") { - t.Error("Column 'updated_at' missing") - } -} - -// KV-5: Index idx_kv_store_created_at exists -func TestSpec_KV5_KvStoreIndex(t *testing.T) { - afs, _ := setupSpecTest(t) - defer afs.Close() - - if !indexExists(t, afs.db, "idx_kv_store_created_at") { - t.Error("Index idx_kv_store_created_at does not exist") - } -} - -// KV-6: Set inserts new key with value -func TestSpec_KV6_SetInsertsNew(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - err := afs.KV.Set(ctx, "new_key", map[string]bool{"test": true}) - if err != nil { - t.Fatalf("Set failed: %v", err) - } - - var val map[string]bool - err = afs.KV.Get(ctx, "new_key", &val) - if err != nil { - t.Fatalf("Get failed: %v", err) - } - - if !val["test"] { - t.Errorf("Expected test=true, got %v", val) - } -} - -// KV-7: Set updates existing key's value (upsert) -func TestSpec_KV7_SetUpdatesExisting(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - _ = afs.KV.Set(ctx, "update_key", "first") - _ = afs.KV.Set(ctx, "update_key", "second") - - var val string - err := afs.KV.Get(ctx, "update_key", &val) - if err != nil { - t.Fatalf("Get failed: %v", err) - } - - if val != "second" { - t.Errorf("Expected 'second', got %q", val) - } -} - -// KV-8: Set preserves created_at on update -func TestSpec_KV8_PreservesCreatedAt(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - _ = afs.KV.Set(ctx, "preserve_key", "first") - - // Get initial created_at - entries, _ := afs.KV.List(ctx, "") - var initialCreatedAt int64 - for _, e := range entries { - if e.Key == "preserve_key" { - initialCreatedAt = e.CreatedAt - break - } - } - - // Wait a moment and update - time.Sleep(10 * time.Millisecond) - _ = afs.KV.Set(ctx, "preserve_key", "second") - - // Get updated entry - entries, _ = afs.KV.List(ctx, "") - for _, e := range entries { - if e.Key == "preserve_key" { - if e.CreatedAt != initialCreatedAt { - t.Error("created_at should be preserved on update") - } - break - } - } -} - -// KV-9: Get returns value for existing key -func TestSpec_KV9_GetExisting(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - _ = afs.KV.Set(ctx, "exists", "value") - - var val string - err := afs.KV.Get(ctx, "exists", &val) - if err != nil { - t.Fatalf("Get failed: %v", err) - } - - if val != "value" { - t.Errorf("Expected 'value', got %q", val) - } -} - -// KV-10: Get returns error for missing key -func TestSpec_KV10_GetMissing(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - var val string - err := afs.KV.Get(ctx, "nonexistent", &val) - if err == nil { - t.Error("Get should return error for missing key") - } -} - -// KV-11: Delete removes key -func TestSpec_KV11_DeleteRemoves(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - _ = afs.KV.Set(ctx, "to_delete", "value") - _ = afs.KV.Delete(ctx, "to_delete") - - var val string - err := afs.KV.Get(ctx, "to_delete", &val) - if err == nil { - t.Error("Key should not exist after delete") - } -} - -// KV-12: List returns all keys ordered by key ASC -func TestSpec_KV12_ListOrdered(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Insert in non-alphabetical order - _ = afs.KV.Set(ctx, "zebra", "z") - _ = afs.KV.Set(ctx, "apple", "a") - _ = afs.KV.Set(ctx, "mango", "m") - - entries, err := afs.KV.List(ctx, "") - if err != nil { - t.Fatalf("List failed: %v", err) - } - - if len(entries) != 3 { - t.Fatalf("Expected 3 entries, got %d", len(entries)) - } - - expected := []string{"apple", "mango", "zebra"} - for i, e := range entries { - if e.Key != expected[i] { - t.Errorf("Entry %d: expected %q, got %q", i, expected[i], e.Key) - } - } -} - -// KV-13: Keys MUST be unique -func TestSpec_KV13_KeysUnique(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - _ = afs.KV.Set(ctx, "unique", "first") - _ = afs.KV.Set(ctx, "unique", "second") - - // Should only have one entry with that key - keys, _ := afs.KV.Keys(ctx, "") - count := 0 - for _, k := range keys { - if k == "unique" { - count++ - } - } - - if count != 1 { - t.Errorf("Expected 1 'unique' key, got %d", count) - } -} - -// KV-14: Values MUST be valid JSON strings -func TestSpec_KV14_ValuesValidJSON(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - // Test various types of values - testCases := []struct { - key string - value interface{} - }{ - {"json_str", "string"}, - {"json_num", 123}, - {"json_bool", true}, - {"json_obj", map[string]string{"key": "value"}}, - {"json_arr", []string{"a", "b", "c"}}, - } - - for _, tc := range testCases { - err := afs.KV.Set(ctx, tc.key, tc.value) - if err != nil { - t.Errorf("Failed to set value for %s: %v", tc.key, err) - } - - // Use GetRaw to verify stored as valid JSON - rawVal, err := afs.KV.GetRaw(ctx, tc.key) - if err != nil { - t.Errorf("Failed to get key %s: %v", tc.key, err) - } - - // Verify it's valid JSON - var parsed interface{} - if err := json.Unmarshal(rawVal, &parsed); err != nil { - t.Errorf("Stored value not valid JSON: %q", string(rawVal)) - } - } -} - -// KV-15: Timestamps MUST use Unix epoch format -func TestSpec_KV15_UnixTimestamps(t *testing.T) { - afs, ctx := setupSpecTest(t) - defer afs.Close() - - now := time.Now().Unix() - _ = afs.KV.Set(ctx, "timestamp_test", "test") - - entries, _ := afs.KV.List(ctx, "") - for _, e := range entries { - if e.Key == "timestamp_test" { - // Timestamps should be within reasonable range of now - if e.CreatedAt < now-60 || e.CreatedAt > now+60 { - t.Errorf("created_at %d not a reasonable Unix timestamp (expected near %d)", e.CreatedAt, now) - } - if e.UpdatedAt < now-60 || e.UpdatedAt > now+60 { - t.Errorf("updated_at %d not a reasonable Unix timestamp (expected near %d)", e.UpdatedAt, now) - } - break - } - } -} diff --git a/sdk/go/symlink_test.go b/sdk/go/symlink_test.go deleted file mode 100644 index f94efbd5..00000000 --- a/sdk/go/symlink_test.go +++ /dev/null @@ -1,384 +0,0 @@ -package agentfs - -import ( - "context" - "errors" - "testing" -) - -func setupSymlinkTest(t *testing.T) (*Filesystem, context.Context) { - t.Helper() - ctx := context.Background() - afs, err := Open(ctx, AgentFSOptions{Path: ":memory:"}) - if err != nil { - t.Fatalf("Failed to open AgentFS: %v", err) - } - t.Cleanup(func() { afs.Close() }) - return afs.FS, ctx -} - -func TestSymlinkResolution(t *testing.T) { - t.Run("Stat follows final symlink", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.WriteFile(ctx, "/target.txt", []byte("hello"), 0o644) - fs.Symlink(ctx, "/target.txt", "/link.txt") - - stats, err := fs.Stat(ctx, "/link.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !stats.IsRegularFile() { - t.Error("Stat on symlink should follow to target (regular file)") - } - if stats.Size != 5 { - t.Errorf("Size = %d, want 5", stats.Size) - } - }) - - t.Run("Lstat does not follow final symlink", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.WriteFile(ctx, "/target.txt", []byte("hello"), 0o644) - fs.Symlink(ctx, "/target.txt", "/link.txt") - - stats, err := fs.Lstat(ctx, "/link.txt") - if err != nil { - t.Fatalf("Lstat failed: %v", err) - } - if !stats.IsSymlink() { - t.Error("Lstat should return symlink stats") - } - }) - - t.Run("Lstat and Stat identical for non-symlinks", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.WriteFile(ctx, "/file.txt", []byte("data"), 0o644) - - stat, err := fs.Stat(ctx, "/file.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - lstat, err := fs.Lstat(ctx, "/file.txt") - if err != nil { - t.Fatalf("Lstat failed: %v", err) - } - if stat.Ino != lstat.Ino { - t.Errorf("Stat.Ino=%d != Lstat.Ino=%d", stat.Ino, lstat.Ino) - } - if stat.Mode != lstat.Mode { - t.Errorf("Stat.Mode=%o != Lstat.Mode=%o", stat.Mode, lstat.Mode) - } - }) - - t.Run("intermediate symlink traversal", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.Mkdir(ctx, "/realdir", 0o755) - fs.WriteFile(ctx, "/realdir/file.txt", []byte("found"), 0o644) - fs.Symlink(ctx, "/realdir", "/dirlink") - - // Access file through the symlinked directory - data, err := fs.ReadFile(ctx, "/dirlink/file.txt") - if err != nil { - t.Fatalf("ReadFile through symlinked dir failed: %v", err) - } - if string(data) != "found" { - t.Errorf("Content = %q, want %q", string(data), "found") - } - - // Stat through symlinked directory - stats, err := fs.Stat(ctx, "/dirlink/file.txt") - if err != nil { - t.Fatalf("Stat through symlinked dir failed: %v", err) - } - if !stats.IsRegularFile() { - t.Error("Expected regular file through symlinked dir") - } - }) - - t.Run("ELOOP circular symlinks", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.Symlink(ctx, "/b", "/a") - fs.Symlink(ctx, "/a", "/b") - - _, err := fs.Stat(ctx, "/a") - if err == nil { - t.Fatal("Expected ELOOP error") - } - if !IsLoop(err) { - t.Errorf("Expected ELOOP, got: %v", err) - } - }) - - t.Run("ELOOP self-referencing symlink", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.Symlink(ctx, "/self", "/self") - - _, err := fs.Stat(ctx, "/self") - if err == nil { - t.Fatal("Expected ELOOP error") - } - if !IsLoop(err) { - t.Errorf("Expected ELOOP, got: %v", err) - } - }) - - t.Run("chain of symlinks", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.WriteFile(ctx, "/target.txt", []byte("end"), 0o644) - fs.Symlink(ctx, "/target.txt", "/c") - fs.Symlink(ctx, "/c", "/b") - fs.Symlink(ctx, "/b", "/a") - - // Stat follows the chain a -> b -> c -> target.txt - stats, err := fs.Stat(ctx, "/a") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if !stats.IsRegularFile() { - t.Error("Expected regular file after following chain") - } - - data, err := fs.ReadFile(ctx, "/a") - if err != nil { - t.Fatalf("ReadFile through chain failed: %v", err) - } - if string(data) != "end" { - t.Errorf("Content = %q, want %q", string(data), "end") - } - }) - - t.Run("relative symlink resolution", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.Mkdir(ctx, "/dir", 0o755) - fs.WriteFile(ctx, "/dir/target.txt", []byte("relative"), 0o644) - // Relative symlink in /dir pointing to sibling file - fs.Symlink(ctx, "target.txt", "/dir/link.txt") - - data, err := fs.ReadFile(ctx, "/dir/link.txt") - if err != nil { - t.Fatalf("ReadFile through relative symlink failed: %v", err) - } - if string(data) != "relative" { - t.Errorf("Content = %q, want %q", string(data), "relative") - } - }) - - t.Run("relative symlink with dotdot", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.WriteFile(ctx, "/root_file.txt", []byte("from root"), 0o644) - fs.Mkdir(ctx, "/subdir", 0o755) - // Symlink in /subdir pointing to ../root_file.txt - fs.Symlink(ctx, "../root_file.txt", "/subdir/up_link.txt") - - data, err := fs.ReadFile(ctx, "/subdir/up_link.txt") - if err != nil { - t.Fatalf("ReadFile through relative dotdot symlink failed: %v", err) - } - if string(data) != "from root" { - t.Errorf("Content = %q, want %q", string(data), "from root") - } - }) - - t.Run("dangling symlink: Stat returns ENOENT, Lstat succeeds", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.Symlink(ctx, "/nonexistent", "/dangling") - - _, err := fs.Stat(ctx, "/dangling") - if err == nil { - t.Fatal("Expected error from Stat on dangling symlink") - } - if !IsNotExist(err) { - t.Errorf("Expected ENOENT, got: %v", err) - } - - stats, err := fs.Lstat(ctx, "/dangling") - if err != nil { - t.Fatalf("Lstat on dangling symlink should succeed: %v", err) - } - if !stats.IsSymlink() { - t.Error("Lstat should show it's a symlink") - } - }) - - t.Run("operations through symlinked directories", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.Mkdir(ctx, "/real", 0o755) - fs.Symlink(ctx, "/real", "/linked") - - // Mkdir through symlink - err := fs.Mkdir(ctx, "/linked/subdir", 0o755) - if err != nil { - t.Fatalf("Mkdir through symlink failed: %v", err) - } - - // Write through symlink - err = fs.WriteFile(ctx, "/linked/subdir/file.txt", []byte("through link"), 0o644) - if err != nil { - t.Fatalf("WriteFile through symlink failed: %v", err) - } - - // Read through original path - data, err := fs.ReadFile(ctx, "/real/subdir/file.txt") - if err != nil { - t.Fatalf("ReadFile from real path failed: %v", err) - } - if string(data) != "through link" { - t.Errorf("Content = %q, want %q", string(data), "through link") - } - - // Unlink through symlink - err = fs.Unlink(ctx, "/linked/subdir/file.txt") - if err != nil { - t.Fatalf("Unlink through symlink failed: %v", err) - } - - // Verify gone from real path - _, err = fs.Stat(ctx, "/real/subdir/file.txt") - if !IsNotExist(err) { - t.Error("File should be gone from real path") - } - }) - - t.Run("Readlink follows intermediate symlinks", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.Mkdir(ctx, "/real", 0o755) - fs.Symlink(ctx, "/somewhere", "/real/inner_link") - fs.Symlink(ctx, "/real", "/dir_link") - - // Readlink /dir_link/inner_link should follow dir_link but not inner_link - target, err := fs.Readlink(ctx, "/dir_link/inner_link") - if err != nil { - t.Fatalf("Readlink failed: %v", err) - } - if target != "/somewhere" { - t.Errorf("Target = %q, want %q", target, "/somewhere") - } - }) - - t.Run("Open follows symlinks", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.WriteFile(ctx, "/openme.txt", []byte("opened"), 0o644) - fs.Symlink(ctx, "/openme.txt", "/open_link.txt") - - f, err := fs.Open(ctx, "/open_link.txt", O_RDONLY) - if err != nil { - t.Fatalf("Open through symlink failed: %v", err) - } - - buf := make([]byte, 64) - n, _ := f.Read(buf) - if string(buf[:n]) != "opened" { - t.Errorf("Content = %q, want %q", string(buf[:n]), "opened") - } - }) - - t.Run("ENOTDIR when intermediate is a regular file", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.WriteFile(ctx, "/notadir", []byte("x"), 0o644) - - _, err := fs.Stat(ctx, "/notadir/child") - if err == nil { - t.Fatal("Expected ENOTDIR error") - } - var fsErr *FSError - if !errors.As(err, &fsErr) || fsErr.Code != ENOTDIR { - t.Errorf("Expected ENOTDIR, got: %v", err) - } - }) - - t.Run("Readdir through symlinked directory", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.Mkdir(ctx, "/dir", 0o755) - fs.WriteFile(ctx, "/dir/a.txt", []byte("a"), 0o644) - fs.WriteFile(ctx, "/dir/b.txt", []byte("b"), 0o644) - fs.Symlink(ctx, "/dir", "/dir_link") - - entries, err := fs.Readdir(ctx, "/dir_link") - if err != nil { - t.Fatalf("Readdir through symlink failed: %v", err) - } - if len(entries) != 2 { - t.Errorf("Expected 2 entries, got %d", len(entries)) - } - }) - - t.Run("Chmod follows symlink", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.WriteFile(ctx, "/chmod_target.txt", []byte("x"), 0o644) - fs.Symlink(ctx, "/chmod_target.txt", "/chmod_link") - - err := fs.Chmod(ctx, "/chmod_link", 0o600) - if err != nil { - t.Fatalf("Chmod through symlink failed: %v", err) - } - - // Verify target's mode changed - stats, err := fs.Stat(ctx, "/chmod_target.txt") - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - if stats.Mode&0o777 != 0o600 { - t.Errorf("Mode = %o, want 0600", stats.Mode&0o777) - } - }) - - t.Run("deep symlink chain at limit", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - // Create a chain of exactly MaxSymlinkDepth symlinks - fs.WriteFile(ctx, "/final_target.txt", []byte("deep"), 0o644) - - prev := "/final_target.txt" - for i := 0; i < MaxSymlinkDepth; i++ { - name := "/chain_" + itoa(i) - fs.Symlink(ctx, prev, name) - prev = name - } - - // The last link should still resolve (exactly MaxSymlinkDepth hops) - stats, err := fs.Stat(ctx, prev) - if err != nil { - t.Fatalf("Stat at MaxSymlinkDepth chain should succeed: %v", err) - } - if !stats.IsRegularFile() { - t.Error("Expected regular file") - } - - // One more symlink should cause ELOOP - fs.Symlink(ctx, prev, "/one_too_many") - _, err = fs.Stat(ctx, "/one_too_many") - if err == nil { - t.Fatal("Expected ELOOP for chain exceeding MaxSymlinkDepth") - } - if !IsLoop(err) { - t.Errorf("Expected ELOOP, got: %v", err) - } - }) - - t.Run("relative symlink as intermediate directory", func(t *testing.T) { - fs, ctx := setupSymlinkTest(t) - fs.MkdirAll(ctx, "/a/b", 0o755) - fs.WriteFile(ctx, "/a/b/file.txt", []byte("nested"), 0o644) - // /a/link -> b (relative, resolves to /a/b) - fs.Symlink(ctx, "b", "/a/link") - - data, err := fs.ReadFile(ctx, "/a/link/file.txt") - if err != nil { - t.Fatalf("ReadFile through relative intermediate symlink failed: %v", err) - } - if string(data) != "nested" { - t.Errorf("Content = %q, want %q", string(data), "nested") - } - }) -} - -// itoa is a simple int to string converter for test use. -func itoa(n int) string { - if n == 0 { - return "0" - } - s := "" - for n > 0 { - s = string(rune('0'+n%10)) + s - n /= 10 - } - return s -} diff --git a/sdk/go/toolcalls.go b/sdk/go/toolcalls.go deleted file mode 100644 index 931cbb86..00000000 --- a/sdk/go/toolcalls.go +++ /dev/null @@ -1,285 +0,0 @@ -package agentfs - -import ( - "context" - "database/sql" - "encoding/json" - "fmt" - "time" -) - -// ToolCalls provides tool call tracking backed by SQLite. -type ToolCalls struct { - db *sql.DB -} - -// PendingCall represents an in-progress tool call. -type PendingCall struct { - tc *ToolCalls - name string - params json.RawMessage - startedAt int64 -} - -// Start begins tracking a tool call. -// Returns a PendingCall that should be completed with Success() or Error(). -func (tc *ToolCalls) Start(ctx context.Context, name string, parameters any) (*PendingCall, error) { - var params json.RawMessage - if parameters != nil { - var err error - params, err = json.Marshal(parameters) - if err != nil { - return nil, fmt.Errorf("failed to marshal parameters: %w", err) - } - } - - return &PendingCall{ - tc: tc, - name: name, - params: params, - startedAt: time.Now().Unix(), - }, nil -} - -// Success marks the pending call as successful and records it. -func (pc *PendingCall) Success(ctx context.Context, result any) (*ToolCall, error) { - var resultJSON json.RawMessage - if result != nil { - var err error - resultJSON, err = json.Marshal(result) - if err != nil { - return nil, fmt.Errorf("failed to marshal result: %w", err) - } - } - - completedAt := time.Now().Unix() - durationMs := (completedAt - pc.startedAt) * 1000 - - var id int64 - var paramsPtr *string - if pc.params != nil { - s := string(pc.params) - paramsPtr = &s - } - - var resultPtr *string - if resultJSON != nil { - s := string(resultJSON) - resultPtr = &s - } - - err := pc.tc.db.QueryRowContext(ctx, toolCallsInsert, - pc.name, paramsPtr, resultPtr, nil, pc.startedAt, completedAt, durationMs, - ).Scan(&id) - if err != nil { - return nil, fmt.Errorf("failed to record tool call: %w", err) - } - - return &ToolCall{ - ID: id, - Name: pc.name, - Parameters: pc.params, - Result: resultJSON, - Error: nil, - StartedAt: pc.startedAt, - CompletedAt: completedAt, - DurationMs: durationMs, - }, nil -} - -// Error marks the pending call as failed and records it. -func (pc *PendingCall) Error(ctx context.Context, err error) (*ToolCall, error) { - completedAt := time.Now().Unix() - durationMs := (completedAt - pc.startedAt) * 1000 - - var paramsPtr *string - if pc.params != nil { - s := string(pc.params) - paramsPtr = &s - } - - errStr := err.Error() - - var id int64 - queryErr := pc.tc.db.QueryRowContext(ctx, toolCallsInsert, - pc.name, paramsPtr, nil, errStr, pc.startedAt, completedAt, durationMs, - ).Scan(&id) - if queryErr != nil { - return nil, fmt.Errorf("failed to record tool call: %w", queryErr) - } - - return &ToolCall{ - ID: id, - Name: pc.name, - Parameters: pc.params, - Result: nil, - Error: &errStr, - StartedAt: pc.startedAt, - CompletedAt: completedAt, - DurationMs: durationMs, - }, nil -} - -// Record inserts a complete tool call record directly. -// This is an alternative to the Start/Success/Error pattern. -func (tc *ToolCalls) Record(ctx context.Context, name string, parameters, result any, errMsg *string, startedAt, completedAt int64) (*ToolCall, error) { - var paramsJSON json.RawMessage - if parameters != nil { - var err error - paramsJSON, err = json.Marshal(parameters) - if err != nil { - return nil, fmt.Errorf("failed to marshal parameters: %w", err) - } - } - - var resultJSON json.RawMessage - if result != nil { - var err error - resultJSON, err = json.Marshal(result) - if err != nil { - return nil, fmt.Errorf("failed to marshal result: %w", err) - } - } - - durationMs := (completedAt - startedAt) * 1000 - - var paramsPtr *string - if paramsJSON != nil { - s := string(paramsJSON) - paramsPtr = &s - } - - var resultPtr *string - if resultJSON != nil { - s := string(resultJSON) - resultPtr = &s - } - - var id int64 - err := tc.db.QueryRowContext(ctx, toolCallsInsert, - name, paramsPtr, resultPtr, errMsg, startedAt, completedAt, durationMs, - ).Scan(&id) - if err != nil { - return nil, fmt.Errorf("failed to record tool call: %w", err) - } - - return &ToolCall{ - ID: id, - Name: name, - Parameters: paramsJSON, - Result: resultJSON, - Error: errMsg, - StartedAt: startedAt, - CompletedAt: completedAt, - DurationMs: durationMs, - }, nil -} - -// Get retrieves a tool call by ID. -func (tc *ToolCalls) Get(ctx context.Context, id int64) (*ToolCall, error) { - var call ToolCall - var params, result, errStr sql.NullString - - err := tc.db.QueryRowContext(ctx, toolCallsGetByID, id).Scan( - &call.ID, &call.Name, ¶ms, &result, &errStr, - &call.StartedAt, &call.CompletedAt, &call.DurationMs, - ) - if err == sql.ErrNoRows { - return nil, fmt.Errorf("tool call not found: %d", id) - } - if err != nil { - return nil, fmt.Errorf("failed to get tool call: %w", err) - } - - if params.Valid { - call.Parameters = json.RawMessage(params.String) - } - if result.Valid { - call.Result = json.RawMessage(result.String) - } - if errStr.Valid { - call.Error = &errStr.String - } - - return &call, nil -} - -// GetByName retrieves tool calls by name. -func (tc *ToolCalls) GetByName(ctx context.Context, name string, limit int) ([]ToolCall, error) { - if limit <= 0 { - limit = 100 - } - - rows, err := tc.db.QueryContext(ctx, toolCallsGetByName, name, limit) - if err != nil { - return nil, fmt.Errorf("failed to query tool calls: %w", err) - } - defer rows.Close() - - return scanToolCalls(rows) -} - -// GetRecent retrieves recent tool calls (since timestamp). -func (tc *ToolCalls) GetRecent(ctx context.Context, since int64, limit int) ([]ToolCall, error) { - if limit <= 0 { - limit = 100 - } - - rows, err := tc.db.QueryContext(ctx, toolCallsGetRecent, since, limit) - if err != nil { - return nil, fmt.Errorf("failed to query tool calls: %w", err) - } - defer rows.Close() - - return scanToolCalls(rows) -} - -// GetStats returns aggregated statistics for tool calls. -func (tc *ToolCalls) GetStats(ctx context.Context) ([]ToolCallStats, error) { - rows, err := tc.db.QueryContext(ctx, toolCallsGetStats) - if err != nil { - return nil, fmt.Errorf("failed to query stats: %w", err) - } - defer rows.Close() - - var stats []ToolCallStats - for rows.Next() { - var s ToolCallStats - if err := rows.Scan(&s.Name, &s.TotalCalls, &s.Successful, &s.Failed, &s.AvgDurationMs); err != nil { - return nil, err - } - stats = append(stats, s) - } - - return stats, rows.Err() -} - -// scanToolCalls scans rows into a slice of ToolCall -func scanToolCalls(rows *sql.Rows) ([]ToolCall, error) { - var calls []ToolCall - for rows.Next() { - var call ToolCall - var params, result, errStr sql.NullString - - if err := rows.Scan( - &call.ID, &call.Name, ¶ms, &result, &errStr, - &call.StartedAt, &call.CompletedAt, &call.DurationMs, - ); err != nil { - return nil, err - } - - if params.Valid { - call.Parameters = json.RawMessage(params.String) - } - if result.Valid { - call.Result = json.RawMessage(result.String) - } - if errStr.Valid { - call.Error = &errStr.String - } - - calls = append(calls, call) - } - - return calls, rows.Err() -} diff --git a/sdk/go/types.go b/sdk/go/types.go deleted file mode 100644 index 8f0c110c..00000000 --- a/sdk/go/types.go +++ /dev/null @@ -1,199 +0,0 @@ -package agentfs - -import ( - "encoding/json" - "time" -) - -// AgentFSOptions configures how AgentFS opens or creates a database -type AgentFSOptions struct { - // ID is the agent identifier. If provided without Path, the database - // will be stored at ~/.agentfs/{id}.db - // Must match pattern: ^[a-zA-Z0-9_-]+$ - ID string - - // Path is an explicit database file path. Takes precedence over ID. - Path string - - // ChunkSize is the size of data chunks in bytes (default: 4096) - // Only used when creating a new database; ignored for existing databases. - ChunkSize int - - // Pool configures the database connection pool. - Pool PoolOptions -} - -// PoolOptions configures the SQLite connection pool. -// These settings control how database/sql manages connections. -type PoolOptions struct { - // MaxOpenConns sets the maximum number of open connections to the database. - // Default: 0 (unlimited). For SQLite, a value of 1 ensures serialized access. - MaxOpenConns int - - // MaxIdleConns sets the maximum number of connections in the idle pool. - // Default: 2 (database/sql default). Set to MaxOpenConns for best performance. - MaxIdleConns int - - // ConnMaxLifetime sets the maximum amount of time a connection may be reused. - // Default: 0 (connections are reused forever). - ConnMaxLifetime time.Duration - - // ConnMaxIdleTime sets the maximum amount of time a connection may be idle. - // Default: 0 (connections are never closed due to idle time). - ConnMaxIdleTime time.Duration -} - -// Stats represents file/directory metadata (matches POSIX stat) -type Stats struct { - Ino int64 `json:"ino"` // Inode number - Mode int64 `json:"mode"` // File type and permissions - Nlink int64 `json:"nlink"` // Number of hard links - UID int64 `json:"uid"` // Owner user ID - GID int64 `json:"gid"` // Owner group ID - Size int64 `json:"size"` // File size in bytes - Atime int64 `json:"atime"` // Last access time (Unix timestamp, seconds) - Mtime int64 `json:"mtime"` // Last modification time (Unix timestamp, seconds) - Ctime int64 `json:"ctime"` // Creation/change time (Unix timestamp, seconds) - Rdev int64 `json:"rdev"` // Device number (for special files) - AtimeNsec int64 `json:"atime_nsec"` // Nanosecond component of atime (0-999999999) - MtimeNsec int64 `json:"mtime_nsec"` // Nanosecond component of mtime (0-999999999) - CtimeNsec int64 `json:"ctime_nsec"` // Nanosecond component of ctime (0-999999999) -} - -// IsDir returns true if this is a directory -func (s *Stats) IsDir() bool { - return (s.Mode & S_IFMT) == S_IFDIR -} - -// IsRegularFile returns true if this is a regular file -func (s *Stats) IsRegularFile() bool { - return (s.Mode & S_IFMT) == S_IFREG -} - -// IsSymlink returns true if this is a symbolic link -func (s *Stats) IsSymlink() bool { - return (s.Mode & S_IFMT) == S_IFLNK -} - -// IsFIFO returns true if this is a FIFO/named pipe -func (s *Stats) IsFIFO() bool { - return (s.Mode & S_IFMT) == S_IFIFO -} - -// IsCharDevice returns true if this is a character device -func (s *Stats) IsCharDevice() bool { - return (s.Mode & S_IFMT) == S_IFCHR -} - -// IsBlockDevice returns true if this is a block device -func (s *Stats) IsBlockDevice() bool { - return (s.Mode & S_IFMT) == S_IFBLK -} - -// IsSocket returns true if this is a socket -func (s *Stats) IsSocket() bool { - return (s.Mode & S_IFMT) == S_IFSOCK -} - -// FileType returns the file type portion of the mode -func (s *Stats) FileType() int64 { - return s.Mode & S_IFMT -} - -// Permissions returns the permission bits of the mode -func (s *Stats) Permissions() int64 { - return s.Mode & 0o777 -} - -// AtimeTime returns atime as a time.Time with nanosecond precision -func (s *Stats) AtimeTime() time.Time { - return time.Unix(s.Atime, s.AtimeNsec) -} - -// MtimeTime returns mtime as a time.Time with nanosecond precision -func (s *Stats) MtimeTime() time.Time { - return time.Unix(s.Mtime, s.MtimeNsec) -} - -// CtimeTime returns ctime as a time.Time with nanosecond precision -func (s *Stats) CtimeTime() time.Time { - return time.Unix(s.Ctime, s.CtimeNsec) -} - -// TimeChange represents a timestamp change request for Utimens. -// Use the constructor functions TimeOmit(), TimeNow(), and TimeSet() to create values. -type TimeChange struct { - kind int // 0=Omit, 1=Now, 2=Set - sec int64 - nsec int64 -} - -const ( - timeOmit = 0 - timeNow = 1 - timeSet = 2 -) - -// TimeOmit returns a TimeChange that leaves the timestamp unchanged. -func TimeOmit() TimeChange { - return TimeChange{kind: timeOmit} -} - -// TimeNow returns a TimeChange that sets the timestamp to the current time. -func TimeNow() TimeChange { - return TimeChange{kind: timeNow} -} - -// TimeSet returns a TimeChange that sets the timestamp to the given value. -func TimeSet(sec, nsec int64) TimeChange { - return TimeChange{kind: timeSet, sec: sec, nsec: nsec} -} - -// IsOmit returns true if this TimeChange leaves the timestamp unchanged. -func (tc TimeChange) IsOmit() bool { return tc.kind == timeOmit } - -// IsNow returns true if this TimeChange sets the timestamp to the current time. -func (tc TimeChange) IsNow() bool { return tc.kind == timeNow } - -// IsSet returns true if this TimeChange sets the timestamp to a specific value. -func (tc TimeChange) IsSet() bool { return tc.kind == timeSet } - -// FilesystemStats represents aggregate filesystem statistics returned by Statfs. -type FilesystemStats struct { - Inodes int64 `json:"inodes"` // Total number of inodes (files, directories, symlinks, etc.) - BytesUsed int64 `json:"bytes_used"` // Total bytes used by file contents -} - -// DirEntry represents a directory entry returned by ReaddirPlus -type DirEntry struct { - Name string `json:"name"` - Stats *Stats `json:"stats"` -} - -// ToolCall represents a recorded tool invocation -type ToolCall struct { - ID int64 `json:"id"` - Name string `json:"name"` - Parameters json.RawMessage `json:"parameters,omitempty"` - Result json.RawMessage `json:"result,omitempty"` - Error *string `json:"error,omitempty"` - StartedAt int64 `json:"started_at"` - CompletedAt int64 `json:"completed_at"` - DurationMs int64 `json:"duration_ms"` -} - -// ToolCallStats represents aggregated statistics for tool calls -type ToolCallStats struct { - Name string `json:"name"` - TotalCalls int64 `json:"total_calls"` - Successful int64 `json:"successful"` - Failed int64 `json:"failed"` - AvgDurationMs float64 `json:"avg_duration_ms"` -} - -// KVEntry represents a key-value pair with metadata -type KVEntry struct { - Key string `json:"key"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} diff --git a/sdk/python/.gitignore b/sdk/python/.gitignore deleted file mode 100644 index 324f53b6..00000000 --- a/sdk/python/.gitignore +++ /dev/null @@ -1,66 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -*.manifest -*.spec - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Virtual environments -venv/ -ENV/ -env/ -.venv - -# IDEs -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# AgentFS databases -.agentfs/ -*.db -*.db-shm -*.db-wal - -# OS -.DS_Store -Thumbs.db diff --git a/sdk/python/MANIFEST.in b/sdk/python/MANIFEST.in deleted file mode 100644 index 5ea71a0a..00000000 --- a/sdk/python/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include README.md -include LICENSE -recursive-include agentfs_sdk *.py -recursive-exclude * __pycache__ -recursive-exclude * *.py[co] diff --git a/sdk/python/README.md b/sdk/python/README.md deleted file mode 100644 index ecd10ed6..00000000 --- a/sdk/python/README.md +++ /dev/null @@ -1,190 +0,0 @@ -# AgentFS Python SDK - -A filesystem and key-value store for AI agents, powered by SQLite and [pyturso](https://pypi.org/project/pyturso/). - -## Installation - -```bash -pip install agentfs-sdk -``` - -## Quick Start - -```python -import asyncio -from agentfs_sdk import AgentFS, AgentFSOptions - -async def main(): - # Open an agent filesystem - agent = await AgentFS.open(AgentFSOptions(id='my-agent')) - - # Use key-value store - await agent.kv.set('config', {'debug': True, 'version': '1.0'}) - config = await agent.kv.get('config') - print(f"Config: {config}") - - # Use filesystem - await agent.fs.write_file('/data/notes.txt', 'Hello, AgentFS!') - content = await agent.fs.read_file('/data/notes.txt') - print(f"Content: {content}") - - # Track tool calls - call_id = await agent.tools.start('search', {'query': 'Python'}) - await agent.tools.success(call_id, {'results': ['result1', 'result2']}) - - # Get statistics - stats = await agent.tools.get_stats() - for stat in stats: - print(f"{stat.name}: {stat.total_calls} calls, {stat.avg_duration_ms:.2f}ms avg") - - # Close the database - await agent.close() - -if __name__ == '__main__': - asyncio.run(main()) -``` - -## Features - -### Key-Value Store - -Simple key-value storage with JSON serialization: - -```python -# Set a value -await agent.kv.set('user:123', {'name': 'Alice', 'age': 30}) - -# Get a value -user = await agent.kv.get('user:123') - -# List by prefix -users = await agent.kv.list('user:') - -# Delete a value -await agent.kv.delete('user:123') -``` - -### Filesystem - -POSIX-like filesystem operations: - -```python -# Write a file (creates parent directories automatically) -await agent.fs.write_file('/data/config.json', '{"key": "value"}') - -# Read a file -content = await agent.fs.read_file('/data/config.json') - -# Read as bytes -data = await agent.fs.read_file('/data/image.png', encoding=None) - -# List directory -entries = await agent.fs.readdir('/data') - -# Get file stats -stats = await agent.fs.stat('/data/config.json') -print(f"Size: {stats.size} bytes") -print(f"Modified: {stats.mtime}") -print(f"Is file: {stats.is_file()}") - -# Delete a file -await agent.fs.delete_file('/data/config.json') -``` - -### Tool Calls Tracking - -Track and analyze tool/function calls: - -```python -# Start a tool call -call_id = await agent.tools.start('search', {'query': 'Python'}) - -# Mark as successful -await agent.tools.success(call_id, {'results': [...]}) - -# Or mark as failed -await agent.tools.error(call_id, 'Connection timeout') - -# Record a completed call -await agent.tools.record( - 'search', - started_at=1234567890, - completed_at=1234567892, - parameters={'query': 'Python'}, - result={'results': [...]} -) - -# Query tool calls -calls = await agent.tools.get_by_name('search', limit=10) -recent = await agent.tools.get_recent(since=1234567890) - -# Get statistics -stats = await agent.tools.get_stats() -for stat in stats: - print(f"{stat.name}: {stat.successful}/{stat.total_calls} successful") -``` - -## Configuration - -### Using Agent ID - -Creates a database at `.agentfs/{id}.db`: - -```python -agent = await AgentFS.open(AgentFSOptions(id='my-agent')) -``` - -### Using Custom Path - -Specify a custom database path: - -```python -agent = await AgentFS.open(AgentFSOptions(path='./data/mydb.db')) -``` - -### Using Both - -You can specify both for clarity: - -```python -agent = await AgentFS.open(AgentFSOptions(id='my-agent', path='./data/mydb.db')) -``` - -## Context Manager Support - -Use AgentFS with async context managers: - -```python -async with await AgentFS.open(AgentFSOptions(id='my-agent')) as agent: - await agent.kv.set('key', 'value') - # Database is automatically closed when exiting the context -``` - -## Development - -### Setup - -```bash -# Install dependencies -uv sync --group dev - -# Run tests -uv run pytest - -# Format code -uv run ruff format agentfs_sdk tests - -# Check code -uv run ruff check agentfs_sdk tests -``` - -## License - -MIT License - see LICENSE file for details. - -## Links - -- [GitHub Repository](https://github.com/tursodatabase/agentfs) -- [TypeScript SDK](https://github.com/tursodatabase/agentfs/tree/main/sdk/typescript) -- [tursodb](https://github.com/tursodatabase/turso) -- [pyturso](https://pypi.org/project/pyturso/) diff --git a/sdk/python/agentfs_sdk/__init__.py b/sdk/python/agentfs_sdk/__init__.py deleted file mode 100644 index 318df090..00000000 --- a/sdk/python/agentfs_sdk/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -"""AgentFS Python SDK - -A filesystem and key-value store for AI agents, powered by SQLite. -""" - -from .agentfs import AgentFS, AgentFSOptions -from .errors import ErrnoException, FsErrorCode, FsSyscall -from .filesystem import S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, Filesystem, Stats -from .kvstore import KvStore -from .toolcalls import ToolCall, ToolCalls, ToolCallStats - -__version__ = "0.6.4" - -__all__ = [ - "AgentFS", - "AgentFSOptions", - "KvStore", - "Filesystem", - "Stats", - "S_IFDIR", - "S_IFLNK", - "S_IFMT", - "S_IFREG", - "ToolCalls", - "ToolCall", - "ToolCallStats", - "ErrnoException", - "FsErrorCode", - "FsSyscall", -] diff --git a/sdk/python/agentfs_sdk/agentfs.py b/sdk/python/agentfs_sdk/agentfs.py deleted file mode 100644 index 94dd195d..00000000 --- a/sdk/python/agentfs_sdk/agentfs.py +++ /dev/null @@ -1,126 +0,0 @@ -"""Main AgentFS class""" - -import os -import re -from dataclasses import dataclass -from typing import Optional - -from turso.aio import Connection, connect - -from .filesystem import Filesystem -from .kvstore import KvStore -from .toolcalls import ToolCalls - - -@dataclass -class AgentFSOptions: - """Configuration options for opening an AgentFS instance - - Attributes: - id: Unique identifier for the agent. - - If provided without `path`: Creates storage at `.agentfs/{id}.db` - - If provided with `path`: Uses the specified path - path: Explicit path to the database file. - - If provided: Uses the specified path directly - - Can be combined with `id` - """ - - id: Optional[str] = None - path: Optional[str] = None - - -class AgentFS: - """AgentFS - A filesystem and key-value store for AI agents - - Provides a unified interface for persistent storage using SQLite, - with support for key-value storage, filesystem operations, and - tool call tracking. - """ - - def __init__(self, db: Connection, kv: KvStore, fs: Filesystem, tools: ToolCalls): - """Private constructor - use AgentFS.open() instead""" - self._db = db - self.kv = kv - self.fs = fs - self.tools = tools - - @staticmethod - async def open(options: AgentFSOptions) -> "AgentFS": - """Open an agent filesystem - - Args: - options: Configuration options (id and/or path required) - - Returns: - Fully initialized AgentFS instance - - Raises: - ValueError: If neither id nor path is provided, or if id contains invalid characters - - Example: - >>> # Using id (creates .agentfs/my-agent.db) - >>> agent = await AgentFS.open(AgentFSOptions(id='my-agent')) - >>> - >>> # Using id with custom path - >>> agent = await AgentFS.open(AgentFSOptions(id='my-agent', path='./data/mydb.db')) - >>> - >>> # Using path only - >>> agent = await AgentFS.open(AgentFSOptions(path='./data/mydb.db')) - """ - # Require at least id or path - if not options.id and not options.path: - raise ValueError("AgentFS.open() requires at least 'id' or 'path'.") - - # Validate agent ID if provided - if options.id and not re.match(r"^[a-zA-Z0-9_-]+$", options.id): - raise ValueError( - "Agent ID must contain only alphanumeric characters, hyphens, and underscores" - ) - - # Determine database path: explicit path takes precedence, otherwise use id-based path - if options.path: - db_path = options.path - else: - # id is guaranteed to be defined here (we checked not id and not path above) - directory = ".agentfs" - if not os.path.exists(directory): - os.makedirs(directory, exist_ok=True) - db_path = f"{directory}/{options.id}.db" - - # Connect to the database to ensure it's created - db = await connect(db_path) - - return await AgentFS.open_with(db) - - @staticmethod - async def open_with(db: Connection) -> "AgentFS": - """Open an AgentFS instance with an existing database connection - - Args: - db: An existing pyturso.aio Connection - - Returns: - Fully initialized AgentFS instance - """ - # Initialize all components in parallel - kv = await KvStore.from_database(db) - fs = await Filesystem.from_database(db) - tools = await ToolCalls.from_database(db) - - return AgentFS(db, kv, fs, tools) - - def get_database(self) -> Connection: - """Get the underlying Database connection""" - return self._db - - async def close(self) -> None: - """Close the database connection""" - await self._db.close() - - async def __aenter__(self) -> "AgentFS": - """Context manager entry""" - return self - - async def __aexit__(self, exc_type, exc, tb) -> None: - """Context manager exit""" - await self.close() diff --git a/sdk/python/agentfs_sdk/constants.py b/sdk/python/agentfs_sdk/constants.py deleted file mode 100644 index d2a6b007..00000000 --- a/sdk/python/agentfs_sdk/constants.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Filesystem constants""" - -# File types for mode field -S_IFMT = 0o170000 # File type mask -S_IFREG = 0o100000 # Regular file -S_IFDIR = 0o040000 # Directory -S_IFLNK = 0o120000 # Symbolic link - -# Default permissions -DEFAULT_FILE_MODE = S_IFREG | 0o644 # Regular file, rw-r--r-- -DEFAULT_DIR_MODE = S_IFDIR | 0o755 # Directory, rwxr-xr-x - -DEFAULT_CHUNK_SIZE = 4096 diff --git a/sdk/python/agentfs_sdk/errors.py b/sdk/python/agentfs_sdk/errors.py deleted file mode 100644 index 5e9ea7e7..00000000 --- a/sdk/python/agentfs_sdk/errors.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Error types for filesystem operations""" - -from typing import Literal, Optional - -# POSIX-style error codes for filesystem operations -FsErrorCode = Literal[ - "ENOENT", # No such file or directory - "EEXIST", # File already exists - "EISDIR", # Is a directory (when file expected) - "ENOTDIR", # Not a directory (when directory expected) - "ENOTEMPTY", # Directory not empty - "EPERM", # Operation not permitted - "EINVAL", # Invalid argument - "ENOSYS", # Function not implemented (use for symlinks) -] - -# Filesystem syscall names for error reporting -# rm, scandir and copyfile are not actual syscalls but used for convenience -FsSyscall = Literal[ - "open", - "stat", - "mkdir", - "rmdir", - "rm", - "unlink", - "rename", - "scandir", - "copyfile", - "access", -] - - -class ErrnoException(Exception): - """Exception with errno-style attributes - - Args: - code: POSIX error code (e.g., 'ENOENT') - syscall: System call name (e.g., 'open') - path: Optional path involved in the error - message: Optional custom message (defaults to code) - - Example: - >>> raise ErrnoException('ENOENT', 'open', '/missing.txt') - ErrnoException: ENOENT: no such file or directory, open '/missing.txt' - """ - - def __init__( - self, - code: FsErrorCode, - syscall: FsSyscall, - path: Optional[str] = None, - message: Optional[str] = None, - ): - base = message if message else code - suffix = f" '{path}'" if path is not None else "" - error_message = f"{code}: {base}, {syscall}{suffix}" - super().__init__(error_message) - self.code = code - self.syscall = syscall - self.path = path diff --git a/sdk/python/agentfs_sdk/filesystem.py b/sdk/python/agentfs_sdk/filesystem.py deleted file mode 100644 index 69eb8bc6..00000000 --- a/sdk/python/agentfs_sdk/filesystem.py +++ /dev/null @@ -1,1136 +0,0 @@ -"""Filesystem implementation""" - -import time -from dataclasses import dataclass -from typing import List, Optional, Union - -from turso.aio import Connection - -from .constants import ( - DEFAULT_CHUNK_SIZE, - DEFAULT_DIR_MODE, - DEFAULT_FILE_MODE, - S_IFDIR, - S_IFLNK, - S_IFMT, - S_IFREG, -) -from .errors import ErrnoException, FsSyscall -from .guards import ( - assert_inode_is_directory, - assert_not_root, - assert_not_symlink_mode, - assert_readable_existing_inode, - assert_readdir_target_inode, - assert_unlink_target_inode, - assert_writable_existing_inode, - get_inode_mode_or_throw, - normalize_rm_options, - throw_enoent_unless_force, -) - -# Re-export constants for backwards compatibility -__all__ = ["Filesystem", "Stats", "S_IFMT", "S_IFREG", "S_IFDIR", "S_IFLNK"] - - -@dataclass -class Stats: - """File/directory statistics - - Attributes: - ino: Inode number - mode: File mode and permissions - nlink: Number of hard links - uid: User ID - gid: Group ID - size: File size in bytes - atime: Access time (Unix timestamp) - mtime: Modification time (Unix timestamp) - ctime: Change time (Unix timestamp) - """ - - ino: int - mode: int - nlink: int - uid: int - gid: int - size: int - atime: int - mtime: int - ctime: int - - def is_file(self) -> bool: - """Check if this is a regular file""" - return (self.mode & S_IFMT) == S_IFREG - - def is_directory(self) -> bool: - """Check if this is a directory""" - return (self.mode & S_IFMT) == S_IFDIR - - def is_symbolic_link(self) -> bool: - """Check if this is a symbolic link""" - return (self.mode & S_IFMT) == S_IFLNK - - -class Filesystem: - """Virtual filesystem backed by SQLite - - Provides a POSIX-like filesystem interface with support for - files, directories, and symbolic links. - """ - - def __init__(self, db: Connection): - """Private constructor - use Filesystem.from_database() instead""" - self._db = db - self._root_ino = 1 - self._chunk_size = DEFAULT_CHUNK_SIZE - - @staticmethod - async def from_database(db: Connection) -> "Filesystem": - """Create a Filesystem from an existing database connection - - Args: - db: An existing pyturso.aio Connection - - Returns: - Fully initialized Filesystem instance - """ - fs = Filesystem(db) - await fs._initialize() - return fs - - def get_chunk_size(self) -> int: - """Get the configured chunk size""" - return self._chunk_size - - async def _initialize(self) -> None: - """Initialize the database schema""" - # Create all tables - await self._db.executescript(""" - CREATE TABLE IF NOT EXISTS fs_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL - ); - - CREATE TABLE IF NOT EXISTS fs_inode ( - ino INTEGER PRIMARY KEY AUTOINCREMENT, - mode INTEGER NOT NULL, - nlink INTEGER NOT NULL DEFAULT 0, - uid INTEGER NOT NULL DEFAULT 0, - gid INTEGER NOT NULL DEFAULT 0, - size INTEGER NOT NULL DEFAULT 0, - atime INTEGER NOT NULL, - mtime INTEGER NOT NULL, - ctime INTEGER NOT NULL, - rdev INTEGER NOT NULL DEFAULT 0, - atime_nsec INTEGER NOT NULL DEFAULT 0, - mtime_nsec INTEGER NOT NULL DEFAULT 0, - ctime_nsec INTEGER NOT NULL DEFAULT 0 - ); - - CREATE TABLE IF NOT EXISTS fs_dentry ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - parent_ino INTEGER NOT NULL, - ino INTEGER NOT NULL, - UNIQUE(parent_ino, name) - ); - - CREATE INDEX IF NOT EXISTS idx_fs_dentry_parent - ON fs_dentry(parent_ino, name); - - CREATE TABLE IF NOT EXISTS fs_data ( - ino INTEGER NOT NULL, - chunk_index INTEGER NOT NULL, - data BLOB NOT NULL, - PRIMARY KEY (ino, chunk_index) - ); - - CREATE TABLE IF NOT EXISTS fs_symlink ( - ino INTEGER PRIMARY KEY, - target TEXT NOT NULL - ); - """) - await self._db.commit() - - # Initialize config and root directory - self._chunk_size = await self._ensure_root() - - async def _ensure_root(self) -> int: - """Ensure config and root directory exist, returns the chunk_size""" - # Ensure chunk_size config exists and get its value - cursor = await self._db.execute("SELECT value FROM fs_config WHERE key = 'chunk_size'") - config = await cursor.fetchone() - - if not config: - await self._db.execute( - "INSERT INTO fs_config (key, value) VALUES ('chunk_size', ?)", - (str(DEFAULT_CHUNK_SIZE),), - ) - await self._db.commit() - chunk_size = DEFAULT_CHUNK_SIZE - else: - chunk_size = int(config[0]) if config[0] else DEFAULT_CHUNK_SIZE - - # Set schema version (keep in sync with AGENTFS_SCHEMA_VERSION in sdk/rust/src/schema.rs) - await self._db.execute( - "INSERT OR REPLACE INTO fs_config (key, value) VALUES ('schema_version', '0.4')" - ) - await self._db.commit() - - # Ensure root directory exists - cursor = await self._db.execute("SELECT ino FROM fs_inode WHERE ino = ?", (self._root_ino,)) - root = await cursor.fetchone() - - if not root: - now = int(time.time()) - await self._db.execute( - """ - INSERT INTO fs_inode (ino, mode, nlink, uid, gid, size, atime, mtime, ctime) - VALUES (?, ?, 1, 0, 0, 0, ?, ?, ?) - """, - (self._root_ino, DEFAULT_DIR_MODE, now, now, now), - ) - await self._db.commit() - - return chunk_size - - def _normalize_path(self, path: str) -> str: - """Normalize a path""" - # Remove trailing slashes except for root - normalized = path.rstrip("/") or "/" - # Ensure leading slash - return normalized if normalized.startswith("/") else "/" + normalized - - def _split_path(self, path: str) -> List[str]: - """Split path into components""" - normalized = self._normalize_path(path) - if normalized == "/": - return [] - return [p for p in normalized.split("/") if p] - - async def _resolve_path(self, path: str) -> Optional[int]: - """Resolve a path to an inode number""" - normalized = self._normalize_path(path) - - # Root directory - if normalized == "/": - return self._root_ino - - parts = self._split_path(normalized) - current_ino = self._root_ino - - # Traverse the path - for name in parts: - cursor = await self._db.execute( - """ - SELECT ino FROM fs_dentry - WHERE parent_ino = ? AND name = ? - """, - (current_ino, name), - ) - result = await cursor.fetchone() - - if not result: - return None - - current_ino = result[0] - - return current_ino - - async def _resolve_parent(self, path: str) -> Optional[tuple]: - """Get parent directory inode and basename from path""" - normalized = self._normalize_path(path) - - if normalized == "/": - return None # Root has no parent - - parts = self._split_path(normalized) - name = parts[-1] - parent_path = "/" if len(parts) == 1 else "/" + "/".join(parts[:-1]) - - parent_ino = await self._resolve_path(parent_path) - - if parent_ino is None: - return None - - return (parent_ino, name) - - async def _create_inode(self, mode: int, uid: int = 0, gid: int = 0) -> int: - """Create an inode - - Note: We use RETURNING clause which requires explicit cursor close - when working with CDC-enabled TursoDB connections. - """ - now = int(time.time()) - cursor = self._db.cursor() - try: - await cursor.execute( - """ - INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime) - VALUES (?, ?, ?, 0, ?, ?, ?) - RETURNING ino - """, - (mode, uid, gid, now, now, now), - ) - row = await cursor.fetchone() - assert row is not None - ino = row[0] - finally: - await cursor.close() - # Commit after cursor is closed - await self._db.commit() - return ino - - async def _create_dentry(self, parent_ino: int, name: str, ino: int) -> None: - """Create a directory entry""" - await self._db.execute( - """ - INSERT INTO fs_dentry (name, parent_ino, ino) - VALUES (?, ?, ?) - """, - (name, parent_ino, ino), - ) - # Increment link count - await self._db.execute( - "UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?", - (ino,), - ) - await self._db.commit() - - async def _ensure_parent_dirs(self, path: str) -> None: - """Ensure parent directories exist""" - parts = self._split_path(path) - - # Remove the filename, keep only directory parts - parts = parts[:-1] - - current_ino = self._root_ino - current_path = "" - - for name in parts: - current_path += "/" + name - - # Check if this directory exists - cursor = await self._db.execute( - """ - SELECT ino FROM fs_dentry - WHERE parent_ino = ? AND name = ? - """, - (current_ino, name), - ) - result = await cursor.fetchone() - - if not result: - # Create directory - dir_ino = await self._create_inode(DEFAULT_DIR_MODE) - await self._create_dentry(current_ino, name, dir_ino) - current_ino = dir_ino - else: - current_ino = result[0] - - async def _get_link_count(self, ino: int) -> int: - """Get link count for an inode""" - cursor = await self._db.execute("SELECT nlink FROM fs_inode WHERE ino = ?", (ino,)) - result = await cursor.fetchone() - return result[0] if result else 0 - - async def _get_inode_mode(self, ino: int) -> Optional[int]: - """Get mode for an inode""" - cursor = await self._db.execute("SELECT mode FROM fs_inode WHERE ino = ?", (ino,)) - row = await cursor.fetchone() - return row[0] if row else None - - async def _resolve_path_or_throw(self, path: str, syscall: FsSyscall) -> tuple[str, int]: - """Resolve path to inode or throw ENOENT""" - normalized_path = self._normalize_path(path) - ino = await self._resolve_path(normalized_path) - if ino is None: - raise ErrnoException( - code="ENOENT", - syscall=syscall, - path=normalized_path, - message="no such file or directory", - ) - return (normalized_path, ino) - - async def write_file( - self, - path: str, - content: Union[str, bytes], - encoding: str = "utf-8", - ) -> None: - """Write content to a file - - Args: - path: Path to the file - content: Content to write (string or bytes) - encoding: Text encoding (default: 'utf-8') - - Example: - >>> await fs.write_file('/data/config.json', '{"key": "value"}') - """ - # Ensure parent directories exist - await self._ensure_parent_dirs(path) - - normalized_path = self._normalize_path(path) - # Check if file already exists - ino = await self._resolve_path(normalized_path) - - if ino is not None: - # Validate existing inode - await assert_writable_existing_inode(self._db, ino, "open", normalized_path) - # Update existing file - await self._update_file_content(ino, content, encoding) - else: - # Create new file - parent = await self._resolve_parent(normalized_path) - if not parent: - raise ErrnoException( - code="ENOENT", - syscall="open", - path=normalized_path, - message="no such file or directory", - ) - - parent_ino, name = parent - - # Ensure parent is a directory - await assert_inode_is_directory(self._db, parent_ino, "open", normalized_path) - - # Create inode - file_ino = await self._create_inode(DEFAULT_FILE_MODE) - - # Create directory entry - await self._create_dentry(parent_ino, name, file_ino) - - # Write content - await self._update_file_content(file_ino, content, encoding) - - async def _update_file_content( - self, ino: int, content: Union[str, bytes], encoding: str = "utf-8" - ) -> None: - """Update file content""" - buffer = content.encode(encoding) if isinstance(content, str) else content - now = int(time.time()) - - # Delete existing data chunks - await self._db.execute("DELETE FROM fs_data WHERE ino = ?", (ino,)) - - # Write data in chunks - if len(buffer) > 0: - chunk_index = 0 - for offset in range(0, len(buffer), self._chunk_size): - chunk = buffer[offset : min(offset + self._chunk_size, len(buffer))] - await self._db.execute( - """ - INSERT INTO fs_data (ino, chunk_index, data) - VALUES (?, ?, ?) - """, - (ino, chunk_index, chunk), - ) - chunk_index += 1 - - # Update inode size and mtime - await self._db.execute( - """ - UPDATE fs_inode - SET size = ?, mtime = ? - WHERE ino = ? - """, - (len(buffer), now, ino), - ) - await self._db.commit() - - async def read_file(self, path: str, encoding: Optional[str] = "utf-8") -> Union[bytes, str]: - """Read content from a file - - Args: - path: Path to the file - encoding: Text encoding (default: 'utf-8'). Set to None to return bytes. - - Returns: - File content as string (if encoding specified) or bytes - - Example: - >>> content = await fs.read_file('/data/config.json') - >>> data = await fs.read_file('/data/image.png', encoding=None) - """ - normalized_path, ino = await self._resolve_path_or_throw(path, "open") - - await assert_readable_existing_inode(self._db, ino, "open", normalized_path) - - # Get all data chunks - cursor = await self._db.execute( - """ - SELECT data FROM fs_data - WHERE ino = ? - ORDER BY chunk_index ASC - """, - (ino,), - ) - rows = await cursor.fetchall() - - if not rows: - combined = b"" - else: - # Concatenate all chunks - combined = b"".join(row[0] for row in rows) - - # Update atime - now = int(time.time()) - await self._db.execute("UPDATE fs_inode SET atime = ? WHERE ino = ?", (now, ino)) - await self._db.commit() - - if encoding: - return combined.decode(encoding) - return combined - - async def readdir(self, path: str) -> List[str]: - """List directory contents - - Args: - path: Path to the directory - - Returns: - List of entry names - - Example: - >>> entries = await fs.readdir('/data') - >>> for entry in entries: - >>> print(entry) - """ - normalized_path, ino = await self._resolve_path_or_throw(path, "scandir") - - await assert_readdir_target_inode(self._db, ino, normalized_path) - - # Get all directory entries - cursor = await self._db.execute( - """ - SELECT name FROM fs_dentry - WHERE parent_ino = ? - ORDER BY name ASC - """, - (ino,), - ) - rows = await cursor.fetchall() - - return [row[0] for row in rows] - - async def unlink(self, path: str) -> None: - """Delete a file (unlink) - - Args: - path: Path to the file - - Example: - >>> await fs.unlink('/data/temp.txt') - """ - normalized_path = self._normalize_path(path) - assert_not_root(normalized_path, "unlink") - normalized_path, ino = await self._resolve_path_or_throw(normalized_path, "unlink") - - await assert_unlink_target_inode(self._db, ino, normalized_path) - - parent = await self._resolve_parent(normalized_path) - # parent is guaranteed to exist here since normalized_path != '/' - assert parent is not None - parent_ino, name = parent - - # Delete the directory entry - await self._db.execute( - """ - DELETE FROM fs_dentry - WHERE parent_ino = ? AND name = ? - """, - (parent_ino, name), - ) - - # Decrement link count - await self._db.execute( - "UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?", - (ino,), - ) - - # Check if this was the last link to the inode - link_count = await self._get_link_count(ino) - if link_count == 0: - # Delete the inode - await self._db.execute("DELETE FROM fs_inode WHERE ino = ?", (ino,)) - - # Delete all data chunks - await self._db.execute("DELETE FROM fs_data WHERE ino = ?", (ino,)) - - await self._db.commit() - - # Backwards-compatible alias - async def delete_file(self, path: str) -> None: - """Delete a file (deprecated, use unlink instead) - - Args: - path: Path to the file - - Example: - >>> await fs.delete_file('/data/temp.txt') - """ - return await self.unlink(path) - - async def stat(self, path: str) -> Stats: - """Get file/directory statistics - - Args: - path: Path to the file or directory - - Returns: - Stats object with file information - - Example: - >>> stats = await fs.stat('/data/config.json') - >>> print(f"Size: {stats.size} bytes") - >>> print(f"Is file: {stats.is_file()}") - """ - normalized_path, ino = await self._resolve_path_or_throw(path, "stat") - - cursor = await self._db.execute( - """ - SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime - FROM fs_inode - WHERE ino = ? - """, - (ino,), - ) - row = await cursor.fetchone() - - if not row: - raise ErrnoException( - code="ENOENT", - syscall="stat", - path=normalized_path, - message="no such file or directory", - ) - - return Stats( - ino=row[0], - mode=row[1], - nlink=row[2], - uid=row[3], - gid=row[4], - size=row[5], - atime=row[6], - mtime=row[7], - ctime=row[8], - ) - - async def mkdir(self, path: str) -> None: - """Create a directory (non-recursive) - - Args: - path: Path to the directory to create - - Example: - >>> await fs.mkdir('/data/new_dir') - """ - normalized_path = self._normalize_path(path) - - existing = await self._resolve_path(normalized_path) - if existing is not None: - raise ErrnoException( - code="EEXIST", - syscall="mkdir", - path=normalized_path, - message="file already exists", - ) - - parent = await self._resolve_parent(normalized_path) - if not parent: - raise ErrnoException( - code="ENOENT", - syscall="mkdir", - path=normalized_path, - message="no such file or directory", - ) - - parent_ino, name = parent - await assert_inode_is_directory(self._db, parent_ino, "mkdir", normalized_path) - - dir_ino = await self._create_inode(DEFAULT_DIR_MODE) - try: - await self._create_dentry(parent_ino, name, dir_ino) - except Exception: - raise ErrnoException( - code="EEXIST", - syscall="mkdir", - path=normalized_path, - message="file already exists", - ) - - async def rmdir(self, path: str) -> None: - """Remove an empty directory - - Args: - path: Path to the directory to remove - - Example: - >>> await fs.rmdir('/data/empty_dir') - """ - normalized_path = self._normalize_path(path) - assert_not_root(normalized_path, "rmdir") - - normalized_path, ino = await self._resolve_path_or_throw(normalized_path, "rmdir") - - mode = await get_inode_mode_or_throw(self._db, ino, "rmdir", normalized_path) - assert_not_symlink_mode(mode, "rmdir", normalized_path) - if (mode & S_IFMT) != S_IFDIR: - raise ErrnoException( - code="ENOTDIR", - syscall="rmdir", - path=normalized_path, - message="not a directory", - ) - - cursor = await self._db.execute( - """ - SELECT 1 as one FROM fs_dentry - WHERE parent_ino = ? - LIMIT 1 - """, - (ino,), - ) - child = await cursor.fetchone() - if child: - raise ErrnoException( - code="ENOTEMPTY", - syscall="rmdir", - path=normalized_path, - message="directory not empty", - ) - - parent = await self._resolve_parent(normalized_path) - if not parent: - raise ErrnoException( - code="EPERM", - syscall="rmdir", - path=normalized_path, - message="operation not permitted", - ) - - parent_ino, name = parent - await self._remove_dentry_and_maybe_inode(parent_ino, name, ino) - - async def rm( - self, - path: str, - force: bool = False, - recursive: bool = False, - ) -> None: - """Remove a file or directory - - Args: - path: Path to remove - force: If True, ignore nonexistent files - recursive: If True, remove directories and their contents recursively - - Example: - >>> await fs.rm('/data/file.txt') - >>> await fs.rm('/data/dir', recursive=True) - """ - normalized_path = self._normalize_path(path) - options = normalize_rm_options({"force": force, "recursive": recursive}) - force = options["force"] - recursive = options["recursive"] - assert_not_root(normalized_path, "rm") - - ino = await self._resolve_path(normalized_path) - if ino is None: - throw_enoent_unless_force(normalized_path, "rm", force) - return - - mode = await get_inode_mode_or_throw(self._db, ino, "rm", normalized_path) - assert_not_symlink_mode(mode, "rm", normalized_path) - - parent = await self._resolve_parent(normalized_path) - if not parent: - raise ErrnoException( - code="EPERM", - syscall="rm", - path=normalized_path, - message="operation not permitted", - ) - - parent_ino, name = parent - - if (mode & S_IFMT) == S_IFDIR: - if not recursive: - raise ErrnoException( - code="EISDIR", - syscall="rm", - path=normalized_path, - message="illegal operation on a directory", - ) - - await self._rm_dir_contents_recursive(ino) - await self._remove_dentry_and_maybe_inode(parent_ino, name, ino) - return - - # Regular file - await self._remove_dentry_and_maybe_inode(parent_ino, name, ino) - - async def _rm_dir_contents_recursive(self, dir_ino: int) -> None: - """Recursively remove directory contents""" - cursor = await self._db.execute( - """ - SELECT name, ino FROM fs_dentry - WHERE parent_ino = ? - ORDER BY name ASC - """, - (dir_ino,), - ) - children = await cursor.fetchall() - - for name, child_ino in children: - mode = await self._get_inode_mode(child_ino) - if mode is None: - # DB inconsistency; treat as already gone - continue - - if (mode & S_IFMT) == S_IFDIR: - await self._rm_dir_contents_recursive(child_ino) - await self._remove_dentry_and_maybe_inode(dir_ino, name, child_ino) - else: - # Not supported yet (symlinks) - assert_not_symlink_mode(mode, "rm", "") - await self._remove_dentry_and_maybe_inode(dir_ino, name, child_ino) - - async def _remove_dentry_and_maybe_inode(self, parent_ino: int, name: str, ino: int) -> None: - """Remove directory entry and inode if last link""" - await self._db.execute( - """ - DELETE FROM fs_dentry - WHERE parent_ino = ? AND name = ? - """, - (parent_ino, name), - ) - - # Decrement link count - await self._db.execute( - "UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?", - (ino,), - ) - - link_count = await self._get_link_count(ino) - if link_count == 0: - await self._db.execute("DELETE FROM fs_inode WHERE ino = ?", (ino,)) - await self._db.execute("DELETE FROM fs_data WHERE ino = ?", (ino,)) - - await self._db.commit() - - async def rename(self, old_path: str, new_path: str) -> None: - """Rename (move) a file or directory - - Args: - old_path: Current path - new_path: New path - - Example: - >>> await fs.rename('/data/old.txt', '/data/new.txt') - """ - old_normalized = self._normalize_path(old_path) - new_normalized = self._normalize_path(new_path) - - # No-op - if old_normalized == new_normalized: - return - - assert_not_root(old_normalized, "rename") - assert_not_root(new_normalized, "rename") - - old_parent = await self._resolve_parent(old_normalized) - if not old_parent: - raise ErrnoException( - code="EPERM", - syscall="rename", - path=old_normalized, - message="operation not permitted", - ) - - new_parent = await self._resolve_parent(new_normalized) - if not new_parent: - raise ErrnoException( - code="ENOENT", - syscall="rename", - path=new_normalized, - message="no such file or directory", - ) - - new_parent_ino, new_name = new_parent - - # Ensure destination parent exists and is a directory - await assert_inode_is_directory(self._db, new_parent_ino, "rename", new_normalized) - - # Begin transaction - # Note: turso.aio doesn't support explicit BEGIN, but execute should be atomic - try: - old_normalized, old_ino = await self._resolve_path_or_throw(old_normalized, "rename") - old_mode = await get_inode_mode_or_throw(self._db, old_ino, "rename", old_normalized) - assert_not_symlink_mode(old_mode, "rename", old_normalized) - old_is_dir = (old_mode & S_IFMT) == S_IFDIR - - # Prevent renaming a directory into its own subtree (would create cycles) - if old_is_dir and new_normalized.startswith(old_normalized + "/"): - raise ErrnoException( - code="EINVAL", - syscall="rename", - path=new_normalized, - message="invalid argument", - ) - - new_ino = await self._resolve_path(new_normalized) - if new_ino is not None: - new_mode = await get_inode_mode_or_throw( - self._db, new_ino, "rename", new_normalized - ) - assert_not_symlink_mode(new_mode, "rename", new_normalized) - new_is_dir = (new_mode & S_IFMT) == S_IFDIR - - if new_is_dir and not old_is_dir: - raise ErrnoException( - code="EISDIR", - syscall="rename", - path=new_normalized, - message="illegal operation on a directory", - ) - if not new_is_dir and old_is_dir: - raise ErrnoException( - code="ENOTDIR", - syscall="rename", - path=new_normalized, - message="not a directory", - ) - - # If replacing a directory, it must be empty - if new_is_dir: - cursor = await self._db.execute( - """ - SELECT 1 as one FROM fs_dentry - WHERE parent_ino = ? - LIMIT 1 - """, - (new_ino,), - ) - child = await cursor.fetchone() - if child: - raise ErrnoException( - code="ENOTEMPTY", - syscall="rename", - path=new_normalized, - message="directory not empty", - ) - - # Remove the destination entry (and inode if this was the last link) - await self._remove_dentry_and_maybe_inode(new_parent_ino, new_name, new_ino) - - # Move the directory entry - old_parent_ino, old_name = old_parent - await self._db.execute( - """ - UPDATE fs_dentry - SET parent_ino = ?, name = ? - WHERE parent_ino = ? AND name = ? - """, - (new_parent_ino, new_name, old_parent_ino, old_name), - ) - - # Update timestamps - now = int(time.time()) - await self._db.execute( - """ - UPDATE fs_inode - SET ctime = ? - WHERE ino = ? - """, - (now, old_ino), - ) - - await self._db.execute( - """ - UPDATE fs_inode - SET mtime = ?, ctime = ? - WHERE ino = ? - """, - (now, now, old_parent_ino), - ) - if new_parent_ino != old_parent_ino: - await self._db.execute( - """ - UPDATE fs_inode - SET mtime = ?, ctime = ? - WHERE ino = ? - """, - (now, now, new_parent_ino), - ) - - await self._db.commit() - except Exception: - # turso.aio doesn't have explicit rollback, changes are rolled back automatically - raise - - async def copy_file(self, src: str, dest: str) -> None: - """Copy a file. Overwrites destination if it exists. - - Args: - src: Source file path - dest: Destination file path - - Example: - >>> await fs.copy_file('/data/src.txt', '/data/dest.txt') - """ - src_normalized = self._normalize_path(src) - dest_normalized = self._normalize_path(dest) - - if src_normalized == dest_normalized: - raise ErrnoException( - code="EINVAL", - syscall="copyfile", - path=dest_normalized, - message="invalid argument", - ) - - # Resolve and validate source - src_normalized, src_ino = await self._resolve_path_or_throw(src_normalized, "copyfile") - await assert_readable_existing_inode(self._db, src_ino, "copyfile", src_normalized) - - cursor = await self._db.execute( - """ - SELECT mode, uid, gid, size FROM fs_inode WHERE ino = ? - """, - (src_ino,), - ) - src_row = await cursor.fetchone() - if not src_row: - raise ErrnoException( - code="ENOENT", - syscall="copyfile", - path=src_normalized, - message="no such file or directory", - ) - - src_mode, src_uid, src_gid, src_size = src_row - - # Destination parent must exist and be a directory - dest_parent = await self._resolve_parent(dest_normalized) - if not dest_parent: - raise ErrnoException( - code="ENOENT", - syscall="copyfile", - path=dest_normalized, - message="no such file or directory", - ) - - dest_parent_ino, dest_name = dest_parent - await assert_inode_is_directory(self._db, dest_parent_ino, "copyfile", dest_normalized) - - try: - now = int(time.time()) - - # If destination exists, it must be a file (overwrite semantics) - dest_ino = await self._resolve_path(dest_normalized) - if dest_ino is not None: - dest_mode = await get_inode_mode_or_throw( - self._db, dest_ino, "copyfile", dest_normalized - ) - assert_not_symlink_mode(dest_mode, "copyfile", dest_normalized) - if (dest_mode & S_IFMT) == S_IFDIR: - raise ErrnoException( - code="EISDIR", - syscall="copyfile", - path=dest_normalized, - message="illegal operation on a directory", - ) - - # Replace destination contents - await self._db.execute("DELETE FROM fs_data WHERE ino = ?", (dest_ino,)) - await self._db.commit() - - # Copy data chunks - cursor = await self._db.execute( - """ - SELECT chunk_index, data FROM fs_data - WHERE ino = ? - ORDER BY chunk_index ASC - """, - (src_ino,), - ) - src_chunks = await cursor.fetchall() - for chunk_index, data in src_chunks: - await self._db.execute( - """ - INSERT INTO fs_data (ino, chunk_index, data) - VALUES (?, ?, ?) - """, - (dest_ino, chunk_index, data), - ) - - await self._db.execute( - """ - UPDATE fs_inode - SET mode = ?, uid = ?, gid = ?, size = ?, mtime = ?, ctime = ? - WHERE ino = ? - """, - (src_mode, src_uid, src_gid, src_size, now, now, dest_ino), - ) - else: - # Create new destination inode + dentry - dest_ino_created = await self._create_inode(src_mode, src_uid, src_gid) - await self._create_dentry(dest_parent_ino, dest_name, dest_ino_created) - - # Copy data chunks - cursor = await self._db.execute( - """ - SELECT chunk_index, data FROM fs_data - WHERE ino = ? - ORDER BY chunk_index ASC - """, - (src_ino,), - ) - src_chunks = await cursor.fetchall() - for chunk_index, data in src_chunks: - await self._db.execute( - """ - INSERT INTO fs_data (ino, chunk_index, data) - VALUES (?, ?, ?) - """, - (dest_ino_created, chunk_index, data), - ) - - await self._db.execute( - """ - UPDATE fs_inode - SET size = ?, mtime = ?, ctime = ? - WHERE ino = ? - """, - (src_size, now, now, dest_ino_created), - ) - - await self._db.commit() - except Exception: - raise - - async def access(self, path: str) -> None: - """Test a user's permissions for the file or directory. - Currently supports existence checks only (F_OK semantics). - - Args: - path: Path to check - - Example: - >>> await fs.access('/data/config.json') - """ - normalized_path = self._normalize_path(path) - ino = await self._resolve_path(normalized_path) - if ino is None: - raise ErrnoException( - code="ENOENT", - syscall="access", - path=normalized_path, - message="no such file or directory", - ) diff --git a/sdk/python/agentfs_sdk/guards.py b/sdk/python/agentfs_sdk/guards.py deleted file mode 100644 index 5b618751..00000000 --- a/sdk/python/agentfs_sdk/guards.py +++ /dev/null @@ -1,199 +0,0 @@ -"""Guard functions for filesystem operations validation""" - -from typing import Any, Dict, Optional - -from turso.aio import Connection - -from .constants import S_IFDIR, S_IFLNK, S_IFMT -from .errors import ErrnoException, FsSyscall - - -async def _get_inode_mode(db: Connection, ino: int) -> Optional[int]: - """Get mode for an inode""" - cursor = await db.execute("SELECT mode FROM fs_inode WHERE ino = ?", (ino,)) - row = await cursor.fetchone() - return row[0] if row else None - - -def _is_dir_mode(mode: int) -> bool: - """Check if mode represents a directory""" - return (mode & S_IFMT) == S_IFDIR - - -async def get_inode_mode_or_throw( - db: Connection, - ino: int, - syscall: FsSyscall, - path: str, -) -> int: - """Get inode mode or throw ENOENT if not found""" - mode = await _get_inode_mode(db, ino) - if mode is None: - raise ErrnoException( - code="ENOENT", - syscall=syscall, - path=path, - message="no such file or directory", - ) - return mode - - -def assert_not_root(path: str, syscall: FsSyscall) -> None: - """Assert that path is not root directory""" - if path == "/": - raise ErrnoException( - code="EPERM", - syscall=syscall, - path=path, - message="operation not permitted on root directory", - ) - - -def normalize_rm_options(options: Optional[Dict[str, Any]]) -> Dict[str, bool]: - """Normalize rm options to ensure force and recursive are booleans""" - return { - "force": options.get("force", False) if options else False, - "recursive": options.get("recursive", False) if options else False, - } - - -def throw_enoent_unless_force(path: str, syscall: FsSyscall, force: bool) -> None: - """Throw ENOENT unless force flag is set""" - if force: - return - raise ErrnoException( - code="ENOENT", - syscall=syscall, - path=path, - message="no such file or directory", - ) - - -def assert_not_symlink_mode(mode: int, syscall: FsSyscall, path: str) -> None: - """Assert that mode does not represent a symlink""" - if (mode & S_IFMT) == S_IFLNK: - raise ErrnoException( - code="ENOSYS", - syscall=syscall, - path=path, - message="symbolic links not supported yet", - ) - - -async def _assert_existing_non_dir_non_symlink_inode( - db: Connection, - ino: int, - syscall: FsSyscall, - full_path_for_error: str, -) -> None: - """Assert inode exists and is neither directory nor symlink""" - mode = await _get_inode_mode(db, ino) - if mode is None: - raise ErrnoException( - code="ENOENT", - syscall=syscall, - path=full_path_for_error, - message="no such file or directory", - ) - if _is_dir_mode(mode): - raise ErrnoException( - code="EISDIR", - syscall=syscall, - path=full_path_for_error, - message="illegal operation on a directory", - ) - assert_not_symlink_mode(mode, syscall, full_path_for_error) - - -async def assert_inode_is_directory( - db: Connection, - ino: int, - syscall: FsSyscall, - full_path_for_error: str, -) -> None: - """Assert that inode is a directory""" - mode = await _get_inode_mode(db, ino) - if mode is None: - raise ErrnoException( - code="ENOENT", - syscall=syscall, - path=full_path_for_error, - message="no such file or directory", - ) - if not _is_dir_mode(mode): - raise ErrnoException( - code="ENOTDIR", - syscall=syscall, - path=full_path_for_error, - message="not a directory", - ) - - -async def assert_writable_existing_inode( - db: Connection, - ino: int, - syscall: FsSyscall, - full_path_for_error: str, -) -> None: - """Assert inode is writable (exists and is not directory/symlink)""" - await _assert_existing_non_dir_non_symlink_inode(db, ino, syscall, full_path_for_error) - - -async def assert_readable_existing_inode( - db: Connection, - ino: int, - syscall: FsSyscall, - full_path_for_error: str, -) -> None: - """Assert inode is readable (exists and is not directory/symlink)""" - await _assert_existing_non_dir_non_symlink_inode(db, ino, syscall, full_path_for_error) - - -async def assert_readdir_target_inode( - db: Connection, - ino: int, - full_path_for_error: str, -) -> None: - """Assert inode is a valid readdir target (directory, not symlink)""" - syscall: FsSyscall = "scandir" - mode = await _get_inode_mode(db, ino) - if mode is None: - raise ErrnoException( - code="ENOENT", - syscall=syscall, - path=full_path_for_error, - message="no such file or directory", - ) - assert_not_symlink_mode(mode, syscall, full_path_for_error) - if not _is_dir_mode(mode): - raise ErrnoException( - code="ENOTDIR", - syscall=syscall, - path=full_path_for_error, - message="not a directory", - ) - - -async def assert_unlink_target_inode( - db: Connection, - ino: int, - full_path_for_error: str, -) -> None: - """Assert inode is a valid unlink target (file, not directory/symlink)""" - syscall: FsSyscall = "unlink" - mode = await _get_inode_mode(db, ino) - if mode is None: - raise ErrnoException( - code="ENOENT", - syscall=syscall, - path=full_path_for_error, - message="no such file or directory", - ) - if _is_dir_mode(mode): - raise ErrnoException( - code="EISDIR", - syscall=syscall, - path=full_path_for_error, - message="illegal operation on a directory", - ) - assert_not_symlink_mode(mode, syscall, full_path_for_error) diff --git a/sdk/python/agentfs_sdk/kvstore.py b/sdk/python/agentfs_sdk/kvstore.py deleted file mode 100644 index 5acbaef2..00000000 --- a/sdk/python/agentfs_sdk/kvstore.py +++ /dev/null @@ -1,136 +0,0 @@ -"""Key-Value Store implementation""" - -import json -from typing import Any, Dict, List, Optional, TypeVar - -from turso.aio import Connection - -T = TypeVar("T") - - -class KvStore: - """Key-Value store backed by SQLite - - Provides a simple key-value interface with JSON serialization - for storing arbitrary Python objects. - """ - - def __init__(self, db: Connection): - """Private constructor - use KvStore.from_database() instead""" - self._db = db - - @staticmethod - async def from_database(db: Connection) -> "KvStore": - """Create a KvStore from an existing database connection - - Args: - db: An existing pyturso.aio Connection - - Returns: - Fully initialized KvStore instance - """ - kv = KvStore(db) - await kv._initialize() - return kv - - async def _initialize(self) -> None: - """Initialize the database schema""" - # Create the key-value store table if it doesn't exist - await self._db.executescript(""" - CREATE TABLE IF NOT EXISTS kv_store ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - created_at INTEGER DEFAULT (unixepoch()), - updated_at INTEGER DEFAULT (unixepoch()) - ); - - CREATE INDEX IF NOT EXISTS idx_kv_store_created_at - ON kv_store(created_at); - """) - await self._db.commit() - - async def set(self, key: str, value: Any) -> None: - """Set a key-value pair - - Args: - key: The key to store - value: The value to store (will be JSON serialized) - - Example: - >>> await kv.set('user:123', {'name': 'Alice', 'age': 30}) - """ - # Serialize the value to JSON - serialized_value = json.dumps(value) - - # Use prepared statement to insert or update - await self._db.execute( - """ - INSERT INTO kv_store (key, value, updated_at) - VALUES (?, ?, unixepoch()) - ON CONFLICT(key) DO UPDATE SET - value = excluded.value, - updated_at = unixepoch() - """, - (key, serialized_value), - ) - await self._db.commit() - - async def get(self, key: str, default: Optional[T] = None) -> Optional[T]: - """Get a value by key - - Args: - key: The key to retrieve - default: Default value if key is not found - - Returns: - The deserialized value, or default if key doesn't exist - - Example: - >>> user = await kv.get('user:123') - >>> if user: - >>> print(user['name']) - """ - cursor = await self._db.execute("SELECT value FROM kv_store WHERE key = ?", (key,)) - row = await cursor.fetchone() - - if not row: - return default - - # Deserialize the JSON value - return json.loads(row[0]) - - async def list(self, prefix: str) -> List[Dict[str, Any]]: - """List all keys matching a prefix - - Args: - prefix: The prefix to match - - Returns: - List of dictionaries with 'key' and 'value' fields - - Example: - >>> users = await kv.list('user:') - >>> for item in users: - >>> print(f"{item['key']}: {item['value']}") - """ - # Escape special characters for LIKE query - escaped = prefix.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") - - cursor = await self._db.execute( - "SELECT key, value FROM kv_store WHERE key LIKE ? ESCAPE '\\'", (escaped + "%",) - ) - rows = await cursor.fetchall() - - return [{"key": row[0], "value": json.loads(row[1])} for row in rows] - - async def delete(self, key: str) -> None: - """Delete a key-value pair - - Args: - key: The key to delete - - Example: - >>> await kv.delete('user:123') - """ - await self._db.execute("DELETE FROM kv_store WHERE key = ?", (key,)) - await self._db.commit() diff --git a/sdk/python/agentfs_sdk/toolcalls.py b/sdk/python/agentfs_sdk/toolcalls.py deleted file mode 100644 index 9725c06d..00000000 --- a/sdk/python/agentfs_sdk/toolcalls.py +++ /dev/null @@ -1,413 +0,0 @@ -"""Tool calls tracking implementation""" - -import json -import time -from dataclasses import dataclass -from typing import Any, List, Literal, Optional - -from turso.aio import Connection - - -@dataclass -class ToolCall: - """Tool call record - - Attributes: - id: Unique identifier for the tool call - name: Name of the tool - parameters: Tool parameters (optional) - result: Tool result (optional) - error: Error message if the call failed (optional) - status: Status of the tool call - started_at: Unix timestamp when the call started - completed_at: Unix timestamp when the call completed (optional) - duration_ms: Duration in milliseconds (optional) - """ - - id: int - name: str - parameters: Optional[Any] = None - result: Optional[Any] = None - error: Optional[str] = None - status: Literal["pending", "success", "error"] = "pending" - started_at: int = 0 - completed_at: Optional[int] = None - duration_ms: Optional[int] = None - - -@dataclass -class ToolCallStats: - """Tool call statistics - - Attributes: - name: Name of the tool - total_calls: Total number of calls - successful: Number of successful calls - failed: Number of failed calls - avg_duration_ms: Average duration in milliseconds - """ - - name: str - total_calls: int - successful: int - failed: int - avg_duration_ms: float - - -class ToolCalls: - """Tool calls tracking backed by SQLite - - Provides tracking and analytics for tool/function calls, - recording timing, parameters, results, and errors. - """ - - def __init__(self, db: Connection): - """Private constructor - use ToolCalls.from_database() instead""" - self._db = db - - @staticmethod - async def from_database(db: Connection) -> "ToolCalls": - """Create a ToolCalls from an existing database connection - - Args: - db: An existing pyturso.aio Connection - - Returns: - Fully initialized ToolCalls instance - """ - tools = ToolCalls(db) - await tools._initialize() - return tools - - async def _initialize(self) -> None: - """Initialize the database schema""" - await self._db.executescript(""" - CREATE TABLE IF NOT EXISTS tool_calls ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - parameters TEXT, - result TEXT, - error TEXT, - status TEXT NOT NULL DEFAULT 'pending', - started_at INTEGER NOT NULL, - completed_at INTEGER, - duration_ms INTEGER - ); - - CREATE INDEX IF NOT EXISTS idx_tool_calls_name - ON tool_calls(name); - - CREATE INDEX IF NOT EXISTS idx_tool_calls_started_at - ON tool_calls(started_at); - """) - await self._db.commit() - - async def start(self, name: str, parameters: Optional[Any] = None) -> int: - """Start a new tool call and mark it as pending - - Args: - name: Name of the tool - parameters: Tool parameters (will be JSON serialized) - - Returns: - ID of the created tool call record - - Example: - >>> call_id = await tools.start('search', {'query': 'Python'}) - - Note: We use RETURNING clause which requires explicit cursor close - when working with CDC-enabled TursoDB connections. - """ - serialized_params = json.dumps(parameters) if parameters is not None else None - started_at = int(time.time()) - - cursor = self._db.cursor() - try: - await cursor.execute( - """ - INSERT INTO tool_calls (name, parameters, status, started_at) - VALUES (?, ?, 'pending', ?) - RETURNING id - """, - (name, serialized_params, started_at), - ) - row = await cursor.fetchone() - assert row is not None - call_id = row[0] - finally: - await cursor.close() - await self._db.commit() - return call_id - - async def success(self, call_id: int, result: Optional[Any] = None) -> None: - """Mark a tool call as successful - - Args: - call_id: ID of the tool call - result: Tool result (will be JSON serialized) - - Example: - >>> await tools.success(call_id, {'results': [...]}) - """ - serialized_result = json.dumps(result) if result is not None else None - completed_at = int(time.time()) - - # Get the started_at time to calculate duration - cursor = await self._db.execute( - "SELECT started_at FROM tool_calls WHERE id = ?", (call_id,) - ) - row = await cursor.fetchone() - - if not row: - raise ValueError(f"Tool call with ID {call_id} not found") - - duration_ms = (completed_at - row[0]) * 1000 - - await self._db.execute( - """ - UPDATE tool_calls - SET status = 'success', result = ?, completed_at = ?, duration_ms = ? - WHERE id = ? - """, - (serialized_result, completed_at, duration_ms, call_id), - ) - await self._db.commit() - - async def error(self, call_id: int, error: str) -> None: - """Mark a tool call as failed - - Args: - call_id: ID of the tool call - error: Error message - - Example: - >>> await tools.error(call_id, 'Connection timeout') - """ - completed_at = int(time.time()) - - # Get the started_at time to calculate duration - cursor = await self._db.execute( - "SELECT started_at FROM tool_calls WHERE id = ?", (call_id,) - ) - row = await cursor.fetchone() - - if not row: - raise ValueError(f"Tool call with ID {call_id} not found") - - duration_ms = (completed_at - row[0]) * 1000 - - await self._db.execute( - """ - UPDATE tool_calls - SET status = 'error', error = ?, completed_at = ?, duration_ms = ? - WHERE id = ? - """, - (error, completed_at, duration_ms, call_id), - ) - await self._db.commit() - - async def record( - self, - name: str, - started_at: int, - completed_at: int, - parameters: Optional[Any] = None, - result: Optional[Any] = None, - error: Optional[str] = None, - ) -> int: - """Record a completed tool call - - Either result or error should be provided, not both. - - Args: - name: Name of the tool - started_at: Unix timestamp when the call started - completed_at: Unix timestamp when the call completed - parameters: Tool parameters (will be JSON serialized) - result: Tool result (will be JSON serialized) - error: Error message if the call failed - - Returns: - ID of the created tool call record - - Example: - >>> call_id = await tools.record( - ... 'search', - ... started_at=1234567890, - ... completed_at=1234567892, - ... parameters={'query': 'Python'}, - ... result={'results': [...]} - ... ) - - Note: We use RETURNING clause which requires explicit cursor close - when working with CDC-enabled TursoDB connections. - """ - serialized_params = json.dumps(parameters) if parameters is not None else None - serialized_result = json.dumps(result) if result is not None else None - duration_ms = (completed_at - started_at) * 1000 - status = "error" if error else "success" - - cursor = self._db.cursor() - try: - await cursor.execute( - """ - INSERT INTO tool_calls ( - name, parameters, result, error, status, - started_at, completed_at, duration_ms - ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - RETURNING id - """, - ( - name, - serialized_params, - serialized_result, - error, - status, - started_at, - completed_at, - duration_ms, - ), - ) - row = await cursor.fetchone() - assert row is not None - call_id = row[0] - finally: - await cursor.close() - await self._db.commit() - return call_id - - async def get(self, call_id: int) -> Optional[ToolCall]: - """Get a specific tool call by ID - - Args: - call_id: ID of the tool call - - Returns: - ToolCall object or None if not found - - Example: - >>> call = await tools.get(123) - >>> if call: - >>> print(f"Tool: {call.name}, Status: {call.status}") - """ - cursor = await self._db.execute("SELECT * FROM tool_calls WHERE id = ?", (call_id,)) - row = await cursor.fetchone() - - if not row: - return None - - return self._row_to_tool_call(row) - - async def get_by_name(self, name: str, limit: Optional[int] = None) -> List[ToolCall]: - """Query tool calls by name - - Args: - name: Name of the tool - limit: Maximum number of results (optional) - - Returns: - List of ToolCall objects, ordered by most recent first - - Example: - >>> calls = await tools.get_by_name('search', limit=10) - >>> for call in calls: - >>> print(f"ID: {call.id}, Status: {call.status}") - """ - limit_clause = f"LIMIT {limit}" if limit is not None else "" - - cursor = await self._db.execute( - f""" - SELECT * FROM tool_calls - WHERE name = ? - ORDER BY started_at DESC - {limit_clause} - """, - (name,), - ) - rows = await cursor.fetchall() - - return [self._row_to_tool_call(row) for row in rows] - - async def get_recent(self, since: int, limit: Optional[int] = None) -> List[ToolCall]: - """Query recent tool calls - - Args: - since: Unix timestamp to filter calls after - limit: Maximum number of results (optional) - - Returns: - List of ToolCall objects, ordered by most recent first - - Example: - >>> # Get calls from the last hour - >>> since = int(time.time()) - 3600 - >>> calls = await tools.get_recent(since) - """ - limit_clause = f"LIMIT {limit}" if limit is not None else "" - - cursor = await self._db.execute( - f""" - SELECT * FROM tool_calls - WHERE started_at > ? - ORDER BY started_at DESC - {limit_clause} - """, - (since,), - ) - rows = await cursor.fetchall() - - return [self._row_to_tool_call(row) for row in rows] - - async def get_stats(self) -> List[ToolCallStats]: - """Get performance statistics for all tools - - Only includes completed calls (success or error), not pending ones. - - Returns: - List of ToolCallStats objects, ordered by total calls descending - - Example: - >>> stats = await tools.get_stats() - >>> for stat in stats: - >>> print(f"{stat.name}: {stat.total_calls} calls, " - >>> f"{stat.avg_duration_ms:.2f}ms avg") - """ - cursor = await self._db.execute(""" - SELECT - name, - COUNT(*) as total_calls, - SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successful, - SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as failed, - AVG(duration_ms) as avg_duration_ms - FROM tool_calls - WHERE status != 'pending' - GROUP BY name - ORDER BY total_calls DESC - """) - rows = await cursor.fetchall() - - return [ - ToolCallStats( - name=row[0], - total_calls=row[1], - successful=row[2], - failed=row[3], - avg_duration_ms=row[4] if row[4] is not None else 0.0, - ) - for row in rows - ] - - def _row_to_tool_call(self, row: tuple) -> ToolCall: - """Helper to convert database row to ToolCall object""" - return ToolCall( - id=row[0], - name=row[1], - parameters=json.loads(row[2]) if row[2] is not None else None, - result=json.loads(row[3]) if row[3] is not None else None, - error=row[4] if row[4] is not None else None, - status=row[5], - started_at=row[6], - completed_at=row[7] if row[7] is not None else None, - duration_ms=row[8] if row[8] is not None else None, - ) diff --git a/sdk/python/examples/filesystem_demo.py b/sdk/python/examples/filesystem_demo.py deleted file mode 100644 index f478d6dc..00000000 --- a/sdk/python/examples/filesystem_demo.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Filesystem example for AgentFS Python SDK""" - -import asyncio -from datetime import datetime - -from agentfs_sdk import AgentFS, AgentFSOptions - - -async def main(): - # Initialize AgentFS with persistent storage - agentfs = await AgentFS.open(AgentFSOptions(id="filesystem-demo")) - - # Write a file - print("Writing file...") - await agentfs.fs.write_file("/documents/readme.txt", "Hello, world!") - - # Read the file - print("\nReading file...") - content = await agentfs.fs.read_file("/documents/readme.txt") - print(f"Content: {content}") - - # Get file stats - print("\nFile stats:") - stats = await agentfs.fs.stat("/documents/readme.txt") - print(f" Inode: {stats.ino}") - print(f" Size: {stats.size} bytes") - print(f" Mode: {oct(stats.mode)}") - print(f" Links: {stats.nlink}") - print(f" Is file: {stats.is_file()}") - print(f" Is directory: {stats.is_directory()}") - print(f" Created: {datetime.fromtimestamp(stats.ctime).isoformat()}") - print(f" Modified: {datetime.fromtimestamp(stats.mtime).isoformat()}") - - # List directory - print("\nListing /documents:") - files = await agentfs.fs.readdir("/documents") - print(f" Files: {files}") - - # Write more files - await agentfs.fs.write_file("/documents/notes.txt", "Some notes") - await agentfs.fs.write_file("/images/photo.jpg", b"binary data here") - - # List root - print("\nListing /:") - root_files = await agentfs.fs.readdir("/") - print(f" Directories: {root_files}") - - # Check directory stats - print("\nDirectory stats for /documents:") - dir_stats = await agentfs.fs.stat("/documents") - print(f" Is directory: {dir_stats.is_directory()}") - print(f" Mode: {oct(dir_stats.mode)}") - - # Close the database - await agentfs.close() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/sdk/python/examples/kvstore_demo.py b/sdk/python/examples/kvstore_demo.py deleted file mode 100644 index 31790cd6..00000000 --- a/sdk/python/examples/kvstore_demo.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Key-Value Store example for AgentFS Python SDK""" - -import asyncio -import json -import time - -from agentfs_sdk import AgentFS, AgentFSOptions - - -async def main(): - # Initialize AgentFS with persistent storage - agentfs = await AgentFS.open(AgentFSOptions(id="kvstore-demo")) - - print("=== KvStore Example ===\n") - - # Example 1: Store and retrieve simple values - print("1. Storing simple values:") - await agentfs.kv.set("username", "alice") - await agentfs.kv.set("age", 30) - await agentfs.kv.set("active", True) - - username = await agentfs.kv.get("username") - age = await agentfs.kv.get("age") - active = await agentfs.kv.get("active") - - print(f" Username: {username}") - print(f" Age: {age}") - print(f" Active: {active}\n") - - # Example 2: Store and retrieve objects - print("2. Storing complex objects:") - user = { - "id": 1, - "name": "Alice Johnson", - "email": "alice@example.com", - "preferences": {"theme": "dark", "notifications": True}, - } - - await agentfs.kv.set("user:1", user) - retrieved_user = await agentfs.kv.get("user:1") - print(f" Stored user: {json.dumps(retrieved_user, indent=2)}\n") - - # Example 3: Store and retrieve arrays - print("3. Storing arrays:") - tags = ["python", "database", "ai", "agent"] - await agentfs.kv.set("tags", tags) - retrieved_tags = await agentfs.kv.get("tags") - assert isinstance(retrieved_tags, list) - print(f" Tags: {', '.join(retrieved_tags)}\n") - - # Example 4: Update existing values - print("4. Updating existing values:") - print(f" Age before update: {await agentfs.kv.get('age')}") - await agentfs.kv.set("age", 31) - print(f" Age after update: {await agentfs.kv.get('age')}\n") - - # Example 5: Delete values - print("5. Deleting values:") - print(f" Username before delete: {await agentfs.kv.get('username')}") - await agentfs.kv.delete("username") - print(f" Username after delete: {await agentfs.kv.get('username')}\n") - - # Example 6: Handle non-existent keys - print("6. Retrieving non-existent keys:") - non_existent = await agentfs.kv.get("does-not-exist") - print(f" Result: {non_existent}\n") - - # Example 7: Use cases for AI agents - print("7. AI Agent use cases:") - - # Session state - await agentfs.kv.set( - "session:current", - {"conversationId": "conv-123", "userId": "user-456", "startTime": int(time.time() * 1000)}, - ) - - # Agent memory - await agentfs.kv.set( - "memory:user-preferences", - {"language": "en", "responseStyle": "concise", "expertise": "intermediate"}, - ) - - # Task queue - await agentfs.kv.set( - "tasks:pending", - [ - {"id": 1, "task": "Process document", "priority": "high"}, - {"id": 2, "task": "Send notification", "priority": "low"}, - ], - ) - - print(f" Session: {json.dumps(await agentfs.kv.get('session:current'), indent=2)}") - print(f" Memory: {json.dumps(await agentfs.kv.get('memory:user-preferences'), indent=2)}") - print(f" Tasks: {json.dumps(await agentfs.kv.get('tasks:pending'), indent=2)}") - - print("\n=== Example Complete ===") - - # Close the database - await agentfs.close() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/sdk/python/examples/toolcalls_demo.py b/sdk/python/examples/toolcalls_demo.py deleted file mode 100644 index d3c81f5a..00000000 --- a/sdk/python/examples/toolcalls_demo.py +++ /dev/null @@ -1,114 +0,0 @@ -"""Tool Calls tracking example for AgentFS Python SDK""" - -import asyncio -import json -import time - -from agentfs_sdk import AgentFS, AgentFSOptions - - -async def main(): - # Create an agent with persistent storage - agentfs = await AgentFS.open(AgentFSOptions(id="toolcalls-demo")) - - print("=== Tool Call Tracking Example ===\n") - - # Example 1: Successful tool call - print("1. Tracking a successful web search:") - start_time1 = int(time.time()) - - # Simulate some work - await asyncio.sleep(0.1) - - end_time1 = int(time.time()) - search_id = await agentfs.tools.record( - "web_search", - start_time1, - end_time1, - parameters={"query": "AI agents and LLMs", "maxResults": 10}, - result={ - "results": [ - {"title": "Understanding AI Agents", "url": "https://example.com/1"}, - {"title": "LLM Best Practices", "url": "https://example.com/2"}, - ], - "count": 2, - }, - ) - print(f" Recorded tool call with ID: {search_id}\n") - - # Example 2: Failed tool call - print("2. Tracking a failed API call:") - start_time2 = int(time.time()) - - await asyncio.sleep(0.05) - - end_time2 = int(time.time()) - api_id = await agentfs.tools.record( - "api_call", - start_time2, - end_time2, - parameters={"endpoint": "/users", "method": "GET"}, - error="Connection timeout after 30s", - ) - print(f" Recorded failed call with ID: {api_id}\n") - - # Example 3: Multiple tool calls - print("3. Tracking multiple database queries:") - for i in range(3): - start = int(time.time()) - await asyncio.sleep(0.02) - end = int(time.time()) - - await agentfs.tools.record( - "database_query", - start, - end, - parameters={"sql": f"SELECT * FROM users WHERE id = {i + 1}"}, - result={"rows": 1}, - ) - print(" Created 3 database query records\n") - - # Example 4: Using start/success/error pattern - print("4. Using start/success pattern:") - call_id = await agentfs.tools.start("data_processing", {"file": "data.csv"}) - await asyncio.sleep(0.05) - await agentfs.tools.success(call_id, {"rows_processed": 1000}) - print(f" Completed tool call {call_id}\n") - - # Query tool calls by name - print("5. Querying tool calls by name:") - searches = await agentfs.tools.get_by_name("web_search") - print(f" Found {len(searches)} web search calls") - if searches: - search = searches[0] - print(f" - Duration: {search.duration_ms}ms") - print(f" - Parameters: {json.dumps(search.parameters)}") - print(f" - Result: {json.dumps(search.result)}") - print() - - # Get recent tool calls - print("6. Getting recent tool calls:") - one_minute_ago = int(time.time()) - 60 - recent = await agentfs.tools.get_recent(one_minute_ago) - print(f" Found {len(recent)} calls in the last minute:") - for tc in recent: - status = "failed" if tc.error else "success" - print(f" - {tc.name} ({status})") - print() - - # Get performance statistics - print("7. Performance statistics:") - stats = await agentfs.tools.get_stats() - print(" Tool Performance:") - for stat in stats: - print(f" - {stat.name}:") - print(f" Total: {stat.total_calls}, Success: {stat.successful}, Failed: {stat.failed}") - print(f" Avg Duration: {stat.avg_duration_ms:.2f}ms") - - # Clean up - await agentfs.close() - print("\n✓ Example completed successfully!") - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml deleted file mode 100644 index 356a6dd8..00000000 --- a/sdk/python/pyproject.toml +++ /dev/null @@ -1,65 +0,0 @@ -[build-system] -requires = ["setuptools>=61.0", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "agentfs-sdk" -version = "0.6.4" -description = "AgentFS Python SDK - A filesystem and key-value store for AI agents" -readme = "README.md" -requires-python = ">=3.10" -license = {text = "MIT"} -keywords = ["ai", "agent", "turso", "sqlite", "key-value", "filesystem"] -authors = [ - {name = "Turso"} -] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - 'Operating System :: POSIX :: Linux', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS', - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Software Development :: Libraries :: Python Modules", -] - -dependencies = [ - "pyturso==0.4.4", -] - -[dependency-groups] -dev = [ - "pytest>=9.0.0", - "pytest-asyncio>=1.3.0", - "ruff>=0.14.0", - "ty>=0.0.1a34", -] - -[project.urls] -Homepage = "https://github.com/tursodatabase/agentfs" -Source = "https://github.com/tursodatabase/agentfs" -Repository = "https://github.com/tursodatabase/agentfs" -Issues = "https://github.com/tursodatabase/agentfs/issues" - -[tool.setuptools.packages.find] -include = ["agentfs_sdk*"] - -[tool.ruff] -line-length = 100 -target-version = "py310" - -[tool.ruff.lint] -select = ["E", "F", "I"] -ignore = [] - -[tool.ruff.format] -quote-style = "double" -indent-style = "space" - -[tool.pytest.ini_options] -asyncio_mode = "auto" -testpaths = ["tests"] diff --git a/sdk/python/tests/__init__.py b/sdk/python/tests/__init__.py deleted file mode 100644 index 9aaf324c..00000000 --- a/sdk/python/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for AgentFS Python SDK""" diff --git a/sdk/python/tests/test_agentfs.py b/sdk/python/tests/test_agentfs.py deleted file mode 100644 index 67bcbe8e..00000000 --- a/sdk/python/tests/test_agentfs.py +++ /dev/null @@ -1,138 +0,0 @@ -"""AgentFS Integration Tests""" - -import os -import tempfile - -import pytest - -from agentfs_sdk import AgentFS, AgentFSOptions - - -@pytest.mark.asyncio -class TestAgentFSIntegration: - """Integration tests for AgentFS""" - - async def test_initialize_with_id(self): - """Should successfully initialize with an id""" - with tempfile.TemporaryDirectory() as tmpdir: - old_cwd = os.getcwd() - os.chdir(tmpdir) - - try: - agent = await AgentFS.open(AgentFSOptions(id="test-agent")) - assert agent is not None - assert isinstance(agent, AgentFS) - await agent.close() - finally: - os.chdir(old_cwd) - - async def test_initialize_with_path(self): - """Should initialize with explicit path""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - agent = await AgentFS.open(AgentFSOptions(path=db_path)) - assert agent is not None - assert isinstance(agent, AgentFS) - await agent.close() - assert os.path.exists(db_path) - - async def test_require_id_or_path(self): - """Should require at least id or path""" - with pytest.raises(ValueError, match="requires at least 'id' or 'path'"): - await AgentFS.open(AgentFSOptions()) - - async def test_multiple_instances_different_ids(self): - """Should allow multiple instances with different ids""" - with tempfile.TemporaryDirectory() as tmpdir: - old_cwd = os.getcwd() - os.chdir(tmpdir) - - try: - agent1 = await AgentFS.open(AgentFSOptions(id="test-agent-1")) - agent2 = await AgentFS.open(AgentFSOptions(id="test-agent-2")) - - assert agent1 is not None - assert agent2 is not None - assert agent1 is not agent2 - - await agent1.close() - await agent2.close() - finally: - os.chdir(old_cwd) - - async def test_database_persistence_to_agentfs_directory(self): - """Should persist database file to .agentfs directory""" - with tempfile.TemporaryDirectory() as tmpdir: - old_cwd = os.getcwd() - os.chdir(tmpdir) - - try: - agent = await AgentFS.open(AgentFSOptions(id="test-agent")) - await agent.close() - - # Check that database file exists in .agentfs directory - db_path = ".agentfs/test-agent.db" - assert os.path.exists(db_path) - finally: - os.chdir(old_cwd) - - async def test_reuse_existing_database(self): - """Should reuse existing database file with same id""" - with tempfile.TemporaryDirectory() as tmpdir: - old_cwd = os.getcwd() - os.chdir(tmpdir) - - try: - # Create first instance and write data - agent1 = await AgentFS.open(AgentFSOptions(id="persistence-test")) - await agent1.kv.set("test", "value1") - await agent1.close() - - # Create second instance with same id - should be able to read the data - agent2 = await AgentFS.open(AgentFSOptions(id="persistence-test")) - value = await agent2.kv.get("test") - - assert agent1 is not None - assert agent2 is not None - assert value == "value1" - - await agent2.close() - finally: - os.chdir(old_cwd) - - async def test_context_manager(self): - """Should work as a context manager""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - - async with await AgentFS.open(AgentFSOptions(path=db_path)) as agent: - await agent.kv.set("test", "value") - value = await agent.kv.get("test") - assert value == "value" - - async def test_get_database(self): - """Should return the underlying database connection""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - agent = await AgentFS.open(AgentFSOptions(path=db_path)) - - db = agent.get_database() - assert db is not None - - await agent.close() - - async def test_validate_id_format(self): - """Should validate agent ID format""" - with tempfile.TemporaryDirectory() as tmpdir: - old_cwd = os.getcwd() - os.chdir(tmpdir) - - try: - # Invalid characters in ID - with pytest.raises(ValueError, match="alphanumeric characters"): - await AgentFS.open(AgentFSOptions(id="invalid id with spaces")) - - with pytest.raises(ValueError, match="alphanumeric characters"): - await AgentFS.open(AgentFSOptions(id="invalid@id")) - finally: - os.chdir(old_cwd) diff --git a/sdk/python/tests/test_basic.py b/sdk/python/tests/test_basic.py deleted file mode 100644 index 1ad8eaf3..00000000 --- a/sdk/python/tests/test_basic.py +++ /dev/null @@ -1,148 +0,0 @@ -"""Basic tests for AgentFS Python SDK""" - -import os -import tempfile - -import pytest - -from agentfs_sdk import AgentFS, AgentFSOptions - - -@pytest.mark.asyncio -async def test_agentfs_open_with_id(): - """Test opening AgentFS with an ID""" - with tempfile.TemporaryDirectory() as tmpdir: - old_cwd = os.getcwd() - os.chdir(tmpdir) - - try: - agentfs = await AgentFS.open(AgentFSOptions(id="test-agent")) - assert agentfs is not None - assert agentfs.kv is not None - assert agentfs.fs is not None - assert agentfs.tools is not None - - await agentfs.close() - finally: - os.chdir(old_cwd) - - -@pytest.mark.asyncio -async def test_agentfs_open_with_path(): - """Test opening AgentFS with a custom path""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - - agentfs = await AgentFS.open(AgentFSOptions(path=db_path)) - assert agentfs is not None - - await agentfs.close() - assert os.path.exists(db_path) - - -@pytest.mark.asyncio -async def test_kvstore_basic(): - """Test basic key-value operations""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - - agentfs = await AgentFS.open(AgentFSOptions(path=db_path)) - - # Set and get - await agentfs.kv.set("test_key", "test_value") - value = await agentfs.kv.get("test_key") - assert value == "test_value" - - # Set complex object - obj = {"name": "Alice", "age": 30} - await agentfs.kv.set("user", obj) - retrieved = await agentfs.kv.get("user") - assert retrieved == obj - - # Delete - await agentfs.kv.delete("test_key") - value = await agentfs.kv.get("test_key") - assert value is None - - await agentfs.close() - - -@pytest.mark.asyncio -async def test_filesystem_basic(): - """Test basic filesystem operations""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - - agentfs = await AgentFS.open(AgentFSOptions(path=db_path)) - - # Write and read file - await agentfs.fs.write_file("/test.txt", "Hello, World!") - content = await agentfs.fs.read_file("/test.txt") - assert content == "Hello, World!" - - # Create nested file (auto-create parent dirs) - await agentfs.fs.write_file("/dir1/dir2/file.txt", "nested") - content = await agentfs.fs.read_file("/dir1/dir2/file.txt") - assert content == "nested" - - # List directory - files = await agentfs.fs.readdir("/dir1") - assert "dir2" in files - - # Get stats - stats = await agentfs.fs.stat("/test.txt") - assert stats.is_file() - assert stats.size == len("Hello, World!") - - await agentfs.close() - - -@pytest.mark.asyncio -async def test_toolcalls_basic(): - """Test basic tool call tracking""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - - agentfs = await AgentFS.open(AgentFSOptions(path=db_path)) - - # Record a tool call - import time - - start = int(time.time()) - end = start + 1 - - call_id = await agentfs.tools.record( - "test_tool", start, end, parameters={"param": "value"}, result={"result": "success"} - ) - - assert call_id > 0 - - # Get the tool call - call = await agentfs.tools.get(call_id) - assert call is not None - assert call.name == "test_tool" - assert call.parameters == {"param": "value"} - assert call.result == {"result": "success"} - assert call.status == "success" - - # Get stats - stats = await agentfs.tools.get_stats() - assert len(stats) == 1 - assert stats[0].name == "test_tool" - assert stats[0].total_calls == 1 - - await agentfs.close() - - -@pytest.mark.asyncio -async def test_context_manager(): - """Test using AgentFS as a context manager""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - - async with await AgentFS.open(AgentFSOptions(path=db_path)) as agentfs: - await agentfs.kv.set("test", "value") - value = await agentfs.kv.get("test") - assert value == "value" - - # Database should be closed after exiting context diff --git a/sdk/python/tests/test_filesystem.py b/sdk/python/tests/test_filesystem.py deleted file mode 100644 index 5154a325..00000000 --- a/sdk/python/tests/test_filesystem.py +++ /dev/null @@ -1,1253 +0,0 @@ -"""Filesystem Integration Tests""" - -import os -import tempfile - -import pytest -from turso.aio import connect - -from agentfs_sdk import ErrnoException, Filesystem - - -@pytest.mark.asyncio -class TestFilesystemWriteOperations: - """Filesystem write operations""" - - async def test_write_and_read_simple_text_file(self): - """Should write and read a simple text file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/test.txt", "Hello, World!") - content = await fs.read_file("/test.txt") - assert content == "Hello, World!" - await db.close() - - async def test_write_files_in_subdirectories(self): - """Should write and read files in subdirectories""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/dir/subdir/file.txt", "nested content") - content = await fs.read_file("/dir/subdir/file.txt") - assert content == "nested content" - await db.close() - - async def test_overwrite_existing_file(self): - """Should overwrite existing file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/overwrite.txt", "original content") - await fs.write_file("/overwrite.txt", "new content") - content = await fs.read_file("/overwrite.txt") - assert content == "new content" - await db.close() - - async def test_handle_empty_file_content(self): - """Should handle empty file content""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/empty.txt", "") - content = await fs.read_file("/empty.txt") - assert content == "" - await db.close() - - async def test_handle_large_file_content(self): - """Should handle large file content""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - large_content = "x" * 100000 - await fs.write_file("/large.txt", large_content) - content = await fs.read_file("/large.txt") - assert content == large_content - await db.close() - - async def test_special_characters_in_content(self): - """Should handle files with special characters in content""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - special_content = "Special chars: \n\t\r\"'\\" - await fs.write_file("/special.txt", special_content) - content = await fs.read_file("/special.txt") - assert content == special_content - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemReadOperations: - """Filesystem read operations""" - - async def test_error_reading_nonexistent_file(self): - """Should throw error when reading non-existent file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - with pytest.raises(ErrnoException): - await fs.read_file("/non-existent.txt") - await db.close() - - async def test_read_multiple_files(self): - """Should read multiple different files""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/file1.txt", "content 1") - await fs.write_file("/file2.txt", "content 2") - await fs.write_file("/file3.txt", "content 3") - - assert await fs.read_file("/file1.txt") == "content 1" - assert await fs.read_file("/file2.txt") == "content 2" - assert await fs.read_file("/file3.txt") == "content 3" - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemDirectoryOperations: - """Filesystem directory operations""" - - async def test_list_files_in_root(self): - """Should list files in root directory""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/file1.txt", "content 1") - await fs.write_file("/file2.txt", "content 2") - await fs.write_file("/file3.txt", "content 3") - - files = await fs.readdir("/") - assert "file1.txt" in files - assert "file2.txt" in files - assert "file3.txt" in files - assert len(files) == 3 - await db.close() - - async def test_list_files_in_subdirectory(self): - """Should list files in subdirectory""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/dir/file1.txt", "content 1") - await fs.write_file("/dir/file2.txt", "content 2") - await fs.write_file("/other/file3.txt", "content 3") - - files = await fs.readdir("/dir") - assert "file1.txt" in files - assert "file2.txt" in files - assert "file3.txt" not in files - assert len(files) == 2 - await db.close() - - async def test_distinguish_files_in_different_directories(self): - """Should distinguish between files in different directories""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/dir1/file.txt", "content 1") - await fs.write_file("/dir2/file.txt", "content 2") - - files1 = await fs.readdir("/dir1") - files2 = await fs.readdir("/dir2") - - assert "file.txt" in files1 - assert "file.txt" in files2 - assert len(files1) == 1 - assert len(files2) == 1 - await db.close() - - async def test_list_subdirectories(self): - """Should list subdirectories within a directory""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/parent/child1/file.txt", "content") - await fs.write_file("/parent/child2/file.txt", "content") - await fs.write_file("/parent/file.txt", "content") - - entries = await fs.readdir("/parent") - assert "file.txt" in entries - assert "child1" in entries - assert "child2" in entries - await db.close() - - async def test_nested_directory_structures(self): - """Should handle nested directory structures""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/a/b/c/d/file.txt", "deep content") - files = await fs.readdir("/a/b/c/d") - assert "file.txt" in files - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemDeleteOperations: - """Filesystem delete operations""" - - async def test_delete_existing_file(self): - """Should delete an existing file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/delete-me.txt", "content") - await fs.delete_file("/delete-me.txt") - with pytest.raises(ErrnoException): - await fs.read_file("/delete-me.txt") - await db.close() - - async def test_delete_nonexistent_file(self): - """Should handle deleting non-existent file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.delete_file("/non-existent.txt") - await db.close() - - async def test_delete_and_update_directory_listing(self): - """Should delete file and update directory listing""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/dir/file1.txt", "content 1") - await fs.write_file("/dir/file2.txt", "content 2") - - await fs.delete_file("/dir/file1.txt") - - files = await fs.readdir("/dir") - assert "file1.txt" not in files - assert "file2.txt" in files - assert len(files) == 1 - await db.close() - - async def test_recreate_deleted_file(self): - """Should allow recreating deleted file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/recreate.txt", "original") - await fs.delete_file("/recreate.txt") - await fs.write_file("/recreate.txt", "new content") - content = await fs.read_file("/recreate.txt") - assert content == "new content" - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemPathHandling: - """Filesystem path handling""" - - async def test_paths_with_trailing_slashes(self): - """Should handle paths with trailing slashes""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/dir/file.txt", "content") - files1 = await fs.readdir("/dir") - files2 = await fs.readdir("/dir/") - assert files1 == files2 - await db.close() - - async def test_paths_with_special_characters(self): - """Should handle paths with special characters""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - special_path = "/dir-with-dash/file_with_underscore.txt" - await fs.write_file(special_path, "content") - content = await fs.read_file(special_path) - assert content == "content" - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemIntegrity: - """Filesystem integrity tests""" - - async def test_maintain_file_hierarchy_integrity(self): - """Should maintain file hierarchy integrity""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/root.txt", "root") - await fs.write_file("/dir1/file.txt", "dir1") - await fs.write_file("/dir2/file.txt", "dir2") - await fs.write_file("/dir1/subdir/file.txt", "subdir") - - assert await fs.read_file("/root.txt") == "root" - assert await fs.read_file("/dir1/file.txt") == "dir1" - assert await fs.read_file("/dir2/file.txt") == "dir2" - assert await fs.read_file("/dir1/subdir/file.txt") == "subdir" - - root_files = await fs.readdir("/") - assert "root.txt" in root_files - assert "dir1" in root_files - assert "dir2" in root_files - await db.close() - - async def test_multiple_files_same_name_different_directories(self): - """Should support multiple files with same name in different directories""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/dir1/config.json", '{"version": 1}') - await fs.write_file("/dir2/config.json", '{"version": 2}') - - assert await fs.read_file("/dir1/config.json") == '{"version": 1}' - assert await fs.read_file("/dir2/config.json") == '{"version": 2}' - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemStandaloneUsage: - """Filesystem standalone usage tests""" - - async def test_work_with_in_memory_database(self): - """Should work with in-memory database""" - db = await connect(":memory:") - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/test.txt", "standalone content") - content = await fs.read_file("/test.txt") - assert content == "standalone content" - await db.close() - - async def test_maintain_isolation_between_instances(self): - """Should maintain isolation between instances""" - db1 = await connect(":memory:") - await db1.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs1 = await Filesystem.from_database(db1) - - db2 = await connect(":memory:") - await db2.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs2 = await Filesystem.from_database(db2) - - await fs1.write_file("/test.txt", "fs1 content") - await fs2.write_file("/test.txt", "fs2 content") - - assert await fs1.read_file("/test.txt") == "fs1 content" - assert await fs2.read_file("/test.txt") == "fs2 content" - - await db1.close() - await db2.close() - - -@pytest.mark.asyncio -class TestFilesystemPersistence: - """Filesystem persistence tests""" - - async def test_persist_across_instances(self): - """Should persist data across Filesystem instances""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/persist.txt", "persistent content") - - new_fs = await Filesystem.from_database(db) - content = await new_fs.read_file("/persist.txt") - assert content == "persistent content" - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemChunkSize: - """Filesystem chunk size tests""" - - async def test_default_chunk_size(self): - """Should have default chunk size of 4096""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - assert fs.get_chunk_size() == 4096 - await db.close() - - async def test_write_file_smaller_than_chunk_size(self): - """Should write file smaller than chunk size""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - # Write a file smaller than chunk_size (100 bytes) - data = "x" * 100 - await fs.write_file("/small.txt", data) - - # Read it back - read_data = await fs.read_file("/small.txt") - assert len(read_data) == 100 - assert read_data == data - await db.close() - - async def test_write_file_exact_chunk_size(self): - """Should write file exactly chunk size""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - chunk_size = fs.get_chunk_size() - # Write exactly chunk_size bytes - data = bytes(i % 256 for i in range(chunk_size)) - await fs.write_file("/exact.txt", data) - - # Read it back - read_data = await fs.read_file("/exact.txt", encoding=None) - assert len(read_data) == chunk_size - await db.close() - - async def test_write_file_over_chunk_size(self): - """Should write file one byte over chunk size""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - chunk_size = fs.get_chunk_size() - # Write chunk_size + 1 bytes - data = bytes(i % 256 for i in range(chunk_size + 1)) - await fs.write_file("/overflow.txt", data) - - # Read it back - read_data = await fs.read_file("/overflow.txt", encoding=None) - assert len(read_data) == chunk_size + 1 - await db.close() - - async def test_write_file_spanning_multiple_chunks(self): - """Should write file spanning multiple chunks""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - chunk_size = fs.get_chunk_size() - # Write ~2.5 chunks worth of data - data_size = int(chunk_size * 2.5) - data = bytes(i % 256 for i in range(data_size)) - await fs.write_file("/multi.txt", data) - - # Read it back - read_data = await fs.read_file("/multi.txt", encoding=None) - assert len(read_data) == data_size - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemDataIntegrity: - """Filesystem data integrity tests""" - - async def test_roundtrip_data_byte_for_byte(self): - """Should roundtrip data byte-for-byte""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - chunk_size = fs.get_chunk_size() - # Create data that spans chunk boundaries with identifiable patterns - data_size = chunk_size * 3 + 123 # Odd size spanning 4 chunks - - data = bytes(i % 256 for i in range(data_size)) - await fs.write_file("/roundtrip.bin", data) - - read_data = await fs.read_file("/roundtrip.bin", encoding=None) - assert len(read_data) == data_size - assert read_data == data - await db.close() - - async def test_handle_binary_data_with_null_bytes(self): - """Should handle binary data with null bytes""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - chunk_size = fs.get_chunk_size() - # Create data with null bytes at chunk boundaries - data = bytearray(chunk_size * 2 + 100) - # Put nulls at the chunk boundary - data[chunk_size - 1] = 0 - data[chunk_size] = 0 - data[chunk_size + 1] = 0 - # Put some non-null bytes around - data[chunk_size - 2] = 0xFF - data[chunk_size + 2] = 0xFF - - await fs.write_file("/nulls.bin", bytes(data)) - read_data = await fs.read_file("/nulls.bin", encoding=None) - - assert read_data[chunk_size - 2] == 0xFF - assert read_data[chunk_size - 1] == 0 - assert read_data[chunk_size] == 0 - assert read_data[chunk_size + 1] == 0 - assert read_data[chunk_size + 2] == 0xFF - await db.close() - - async def test_preserve_chunk_ordering(self): - """Should preserve chunk ordering""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - chunk_size = fs.get_chunk_size() - # Create sequential bytes spanning multiple chunks - data_size = chunk_size * 5 - data = bytes(i % 256 for i in range(data_size)) - await fs.write_file("/sequential.bin", data) - - read_data = await fs.read_file("/sequential.bin", encoding=None) - - # Verify every byte is in the correct position - for i in range(data_size): - assert read_data[i] == i % 256 - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemEdgeCases: - """Filesystem edge case tests""" - - async def test_empty_file_with_zero_chunks(self): - """Should handle empty file with zero chunks""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - # Write empty file - await fs.write_file("/empty.txt", "") - - # Read it back - read_data = await fs.read_file("/empty.txt") - assert read_data == "" - - # Verify size is 0 - stats = await fs.stat("/empty.txt") - assert stats.size == 0 - await db.close() - - async def test_overwrite_large_file_with_smaller(self): - """Should overwrite large file with smaller file and clean up chunks""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - chunk_size = fs.get_chunk_size() - - # Write initial large file (3 chunks) - initial_data = bytes(i % 256 for i in range(chunk_size * 3)) - await fs.write_file("/overwrite.txt", initial_data) - - # Overwrite with smaller file (1 chunk) - new_data = "x" * 100 - await fs.write_file("/overwrite.txt", new_data) - - # Verify old chunks are gone and new data is correct - read_data = await fs.read_file("/overwrite.txt") - assert read_data == new_data - - # Verify size is updated - stats = await fs.stat("/overwrite.txt") - assert stats.size == 100 - await db.close() - - async def test_overwrite_small_file_with_larger(self): - """Should overwrite small file with larger file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - chunk_size = fs.get_chunk_size() - - # Write initial small file (1 chunk) - initial_data = "x" * 100 - await fs.write_file("/grow.txt", initial_data) - - # Overwrite with larger file (3 chunks) - new_data = bytes(i % 256 for i in range(chunk_size * 3)) - await fs.write_file("/grow.txt", new_data) - - # Verify data is correct - read_data = await fs.read_file("/grow.txt", encoding=None) - assert len(read_data) == chunk_size * 3 - await db.close() - - async def test_very_large_file(self): - """Should handle very large file (1MB)""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - # Write 1MB file - data_size = 1024 * 1024 - data = bytes(i % 256 for i in range(data_size)) - await fs.write_file("/large.bin", data) - - read_data = await fs.read_file("/large.bin", encoding=None) - assert len(read_data) == data_size - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemStats: - """Filesystem stats tests""" - - async def test_stat_file(self): - """Should get file statistics""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - content = "Hello, World!" - await fs.write_file("/test.txt", content) - - stats = await fs.stat("/test.txt") - assert stats.is_file() - assert not stats.is_directory() - assert stats.size == len(content) - assert stats.ino > 0 - await db.close() - - async def test_stat_directory(self): - """Should get directory statistics""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/dir/file.txt", "content") - - stats = await fs.stat("/dir") - assert stats.is_directory() - assert not stats.is_file() - await db.close() - - async def test_stat_nonexistent_path(self): - """Should throw error for non-existent path""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.stat("/nonexistent") - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemMkdir: - """Tests for mkdir() operation""" - - async def test_create_directory(self): - """Should create a directory with mkdir()""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.mkdir("/newdir") - entries = await fs.readdir("/") - assert "newdir" in entries - await db.close() - - async def test_mkdir_throws_eexist_for_existing_directory(self): - """Should throw EEXIST when mkdir() is called on an existing directory""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.mkdir("/exists") - with pytest.raises(ErrnoException, match="EEXIST"): - await fs.mkdir("/exists") - await db.close() - - async def test_mkdir_throws_enoent_for_missing_parent(self): - """Should throw ENOENT when parent directory does not exist""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.mkdir("/missing-parent/child") - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemRm: - """Tests for rm() operation""" - - async def test_remove_file(self): - """Should remove a file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/rmfile.txt", "content") - await fs.rm("/rmfile.txt") - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.read_file("/rmfile.txt") - await db.close() - - async def test_rm_force_does_not_throw_for_missing_file(self): - """Should not throw when force=True and path does not exist""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - # Should not raise - await fs.rm("/does-not-exist", force=True) - await db.close() - - async def test_rm_throws_enoent_without_force(self): - """Should throw ENOENT when force=False and path does not exist""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.rm("/does-not-exist") - await db.close() - - async def test_rm_throws_eisdir_for_directory_without_recursive(self): - """Should throw EISDIR when trying to rm a directory without recursive""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.mkdir("/rmdir") - with pytest.raises(ErrnoException, match="EISDIR"): - await fs.rm("/rmdir") - await db.close() - - async def test_rm_recursive_removes_directory_tree(self): - """Should remove a directory recursively""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/tree/a/b/c.txt", "content") - await fs.rm("/tree", recursive=True) - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.readdir("/tree") - root = await fs.readdir("/") - assert "tree" not in root - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemRmdir: - """Tests for rmdir() operation""" - - async def test_remove_empty_directory(self): - """Should remove an empty directory""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.mkdir("/emptydir") - await fs.rmdir("/emptydir") - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.readdir("/emptydir") - root = await fs.readdir("/") - assert "emptydir" not in root - await db.close() - - async def test_rmdir_throws_enotempty_for_non_empty_directory(self): - """Should throw ENOTEMPTY when directory is not empty""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/nonempty/file.txt", "content") - with pytest.raises(ErrnoException, match="ENOTEMPTY"): - await fs.rmdir("/nonempty") - await db.close() - - async def test_rmdir_throws_enotdir_for_file(self): - """Should throw ENOTDIR when path is a file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/afile", "content") - with pytest.raises(ErrnoException, match="ENOTDIR"): - await fs.rmdir("/afile") - await db.close() - - async def test_rmdir_throws_eperm_for_root(self): - """Should throw EPERM when attempting to remove root""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - with pytest.raises(ErrnoException, match="EPERM"): - await fs.rmdir("/") - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemRename: - """Tests for rename() operation""" - - async def test_rename_file(self): - """Should rename a file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/a.txt", "hello") - await fs.rename("/a.txt", "/b.txt") - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.read_file("/a.txt") - content = await fs.read_file("/b.txt", "utf-8") - assert content == "hello" - await db.close() - - async def test_rename_directory_preserves_contents(self): - """Should rename a directory and preserve its contents""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/olddir/sub/file.txt", "content") - await fs.rename("/olddir", "/newdir") - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.readdir("/olddir") - content = await fs.read_file("/newdir/sub/file.txt", "utf-8") - assert content == "content" - await db.close() - - async def test_rename_overwrites_destination_file(self): - """Should overwrite destination file if it exists""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/src.txt", "src") - await fs.write_file("/dst.txt", "dst") - await fs.rename("/src.txt", "/dst.txt") - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.read_file("/src.txt") - content = await fs.read_file("/dst.txt", "utf-8") - assert content == "src" - await db.close() - - async def test_rename_throws_eisdir_for_file_to_directory(self): - """Should throw EISDIR when renaming a file onto a directory""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/dir/file.txt", "content") - await fs.write_file("/file.txt", "content") - with pytest.raises(ErrnoException, match="EISDIR"): - await fs.rename("/file.txt", "/dir") - await db.close() - - async def test_rename_throws_enotdir_for_directory_to_file(self): - """Should throw ENOTDIR when renaming a directory onto a file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.mkdir("/somedir") - await fs.write_file("/somefile", "content") - with pytest.raises(ErrnoException, match="ENOTDIR"): - await fs.rename("/somedir", "/somefile") - await db.close() - - async def test_rename_replaces_empty_directory(self): - """Should replace an existing empty directory""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.mkdir("/fromdir") - await fs.mkdir("/todir") - await fs.rename("/fromdir", "/todir") - root = await fs.readdir("/") - assert "todir" in root - assert "fromdir" not in root - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.readdir("/fromdir") - await db.close() - - async def test_rename_throws_enotempty_for_non_empty_destination(self): - """Should throw ENOTEMPTY when replacing a non-empty directory""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.mkdir("/fromdir") - await fs.write_file("/todir/file.txt", "content") - with pytest.raises(ErrnoException, match="ENOTEMPTY"): - await fs.rename("/fromdir", "/todir") - await db.close() - - async def test_rename_throws_eperm_for_root(self): - """Should throw EPERM when attempting to rename root""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - with pytest.raises(ErrnoException, match="EPERM"): - await fs.rename("/", "/x") - await db.close() - - async def test_rename_throws_einval_for_directory_into_subdirectory(self): - """Should throw EINVAL when renaming a directory into its own subdirectory""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/cycle/sub/file.txt", "content") - with pytest.raises(ErrnoException, match="EINVAL"): - await fs.rename("/cycle", "/cycle/sub/moved") - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemCopyFile: - """Tests for copy_file() operation""" - - async def test_copy_file(self): - """Should copy a file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/src.txt", "hello") - await fs.copy_file("/src.txt", "/dst.txt") - src_content = await fs.read_file("/src.txt", "utf-8") - dst_content = await fs.read_file("/dst.txt", "utf-8") - assert src_content == "hello" - assert dst_content == "hello" - await db.close() - - async def test_copy_file_overwrites_destination(self): - """Should overwrite destination if it exists""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/src.txt", "src") - await fs.write_file("/dst.txt", "dst") - await fs.copy_file("/src.txt", "/dst.txt") - dst_content = await fs.read_file("/dst.txt", "utf-8") - assert dst_content == "src" - await db.close() - - async def test_copy_file_throws_enoent_for_missing_source(self): - """Should throw ENOENT when source does not exist""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.copy_file("/nope.txt", "/out.txt") - await db.close() - - async def test_copy_file_throws_enoent_for_missing_destination_parent(self): - """Should throw ENOENT when destination parent does not exist""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/src3.txt", "content") - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.copy_file("/src3.txt", "/missing/child.txt") - await db.close() - - async def test_copy_file_throws_eisdir_for_directory_source(self): - """Should throw EISDIR when source is a directory""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.mkdir("/asrcdir") - with pytest.raises(ErrnoException, match="EISDIR"): - await fs.copy_file("/asrcdir", "/out2.txt") - await db.close() - - async def test_copy_file_throws_eisdir_for_directory_destination(self): - """Should throw EISDIR when destination is a directory""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/src4.txt", "content") - await fs.mkdir("/adstdir") - with pytest.raises(ErrnoException, match="EISDIR"): - await fs.copy_file("/src4.txt", "/adstdir") - await db.close() - - async def test_copy_file_throws_einval_for_same_source_and_destination(self): - """Should throw EINVAL when source and destination are the same""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/same.txt", "content") - with pytest.raises(ErrnoException, match="EINVAL"): - await fs.copy_file("/same.txt", "/same.txt") - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemAccess: - """Tests for access() operation""" - - async def test_access_existing_file(self): - """Should resolve when a file exists""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/exists.txt", "content") - # Should not raise - await fs.access("/exists.txt") - await db.close() - - async def test_access_existing_directory(self): - """Should resolve when a directory exists""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.mkdir("/existsdir") - # Should not raise - await fs.access("/existsdir") - await db.close() - - async def test_access_throws_enoent_for_nonexistent_path(self): - """Should throw ENOENT when path does not exist""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - with pytest.raises(ErrnoException, match="ENOENT"): - await fs.access("/does-not-exist") - await db.close() - - -@pytest.mark.asyncio -class TestFilesystemErrorCodes: - """Tests for error code validation on existing methods""" - - async def test_write_file_throws_eisdir_for_directory(self): - """Should throw EISDIR when attempting to write to a directory path""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/dir/file.txt", "content") - with pytest.raises(ErrnoException, match="EISDIR"): - await fs.write_file("/dir", "nope") - await db.close() - - async def test_write_file_throws_enotdir_for_file_in_path(self): - """Should throw ENOTDIR when a parent path component is a file""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/a", "file-content") - with pytest.raises(ErrnoException, match="ENOTDIR"): - await fs.write_file("/a/b.txt", "child") - await db.close() - - async def test_read_file_throws_eisdir_for_directory(self): - """Should throw EISDIR when attempting to read a directory path""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/dir/file.txt", "content") - with pytest.raises(ErrnoException, match="EISDIR"): - await fs.read_file("/dir") - await db.close() - - async def test_readdir_throws_enotdir_for_file(self): - """Should throw ENOTDIR when attempting to readdir a file path""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/notadir.txt", "content") - with pytest.raises(ErrnoException, match="ENOTDIR"): - await fs.readdir("/notadir.txt") - await db.close() - - async def test_unlink_throws_eisdir_for_directory(self): - """Should throw EISDIR when attempting to unlink a directory""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - fs = await Filesystem.from_database(db) - - await fs.write_file("/adir/file.txt", "content") - with pytest.raises(ErrnoException, match="EISDIR"): - await fs.unlink("/adir") - await db.close() diff --git a/sdk/python/tests/test_kvstore.py b/sdk/python/tests/test_kvstore.py deleted file mode 100644 index afc80416..00000000 --- a/sdk/python/tests/test_kvstore.py +++ /dev/null @@ -1,314 +0,0 @@ -"""KvStore Integration Tests""" - -import os -import tempfile - -import pytest -from turso.aio import connect - -from agentfs_sdk import KvStore - - -@pytest.mark.asyncio -class TestKvStoreBasicOperations: - """Basic KvStore operations""" - - async def test_set_and_get_string(self): - """Should set and get a string value""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - await kv.set("test-key", "test-value") - value = await kv.get("test-key") - assert value == "test-value" - await db.close() - - async def test_set_and_get_object(self): - """Should set and get an object value""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - test_object = {"name": "test", "count": 42, "nested": {"value": True}} - await kv.set("object-key", test_object) - value = await kv.get("object-key") - assert value == test_object - await db.close() - - async def test_set_and_get_number(self): - """Should set and get a number value""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - await kv.set("number-key", 12345) - value = await kv.get("number-key") - assert value == 12345 - await db.close() - - async def test_set_and_get_boolean(self): - """Should set and get a boolean value""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - await kv.set("bool-key", True) - value = await kv.get("bool-key") - assert value is True - await db.close() - - async def test_set_and_get_array(self): - """Should set and get an array value""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - test_array = [1, 2, "three", {"four": 4}] - await kv.set("array-key", test_array) - value = await kv.get("array-key") - assert value == test_array - await db.close() - - async def test_set_and_list_values(self): - """Should set and list values""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - await kv.set("g1:k1", 1) - await kv.set("g1:k2", 2) - await kv.set("g2:k1", 3) - await kv.set("g2:k2", 4) - - result1 = await kv.list("g1:") - assert result1 == [{"key": "g1:k1", "value": 1}, {"key": "g1:k2", "value": 2}] - - result2 = await kv.list("g1:k1") - assert result2 == [{"key": "g1:k1", "value": 1}] - - result3 = await kv.list("g1:k3") - assert result3 == [] - - result4 = await kv.list("g2:") - assert result4 == [{"key": "g2:k1", "value": 3}, {"key": "g2:k2", "value": 4}] - - await db.close() - - -@pytest.mark.asyncio -class TestKvStoreUpdateOperations: - """KvStore update operations""" - - async def test_update_existing_value(self): - """Should update an existing value""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - await kv.set("update-key", "initial-value") - await kv.set("update-key", "updated-value") - value = await kv.get("update-key") - assert value == "updated-value" - await db.close() - - async def test_update_value_type(self): - """Should update value type""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - await kv.set("type-key", "string-value") - await kv.set("type-key", {"object": "value"}) - value = await kv.get("type-key") - assert value == {"object": "value"} - await db.close() - - -@pytest.mark.asyncio -class TestKvStoreDeleteOperations: - """KvStore delete operations""" - - async def test_delete_existing_key(self): - """Should delete an existing key""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - await kv.set("delete-key", "value-to-delete") - await kv.delete("delete-key") - value = await kv.get("delete-key") - assert value is None - await db.close() - - async def test_delete_nonexistent_key(self): - """Should handle deleting non-existent key""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - # Should not throw an error when deleting a non-existent key - await kv.delete("non-existent-key") - await db.close() - - -@pytest.mark.asyncio -class TestKvStoreEdgeCases: - """KvStore edge cases""" - - async def test_get_nonexistent_key(self): - """Should return None for non-existent key""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - value = await kv.get("non-existent-key") - assert value is None - await db.close() - - async def test_get_with_default(self): - """Should return default value for non-existent key""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - value = await kv.get("non-existent-key", default="default-value") - assert value == "default-value" - await db.close() - - async def test_handle_null_values(self): - """Should handle null values""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - await kv.set("null-key", None) - value = await kv.get("null-key") - assert value is None - await db.close() - - async def test_handle_empty_string(self): - """Should handle empty string""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - await kv.set("empty-key", "") - value = await kv.get("empty-key") - assert value == "" - await db.close() - - async def test_handle_zero_value(self): - """Should handle zero value""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - await kv.set("zero-key", 0) - value = await kv.get("zero-key") - assert value == 0 - await db.close() - - async def test_keys_with_special_characters(self): - """Should handle keys with special characters""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - special_key = "key:with/special.chars@123" - await kv.set(special_key, "value") - value = await kv.get(special_key) - assert value == "value" - await db.close() - - -@pytest.mark.asyncio -class TestKvStoreLargeData: - """KvStore large data tests""" - - async def test_large_string_values(self): - """Should handle large string values""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - large_string = "x" * 10000 - await kv.set("large-string", large_string) - value = await kv.get("large-string") - assert value == large_string - await db.close() - - async def test_large_object_values(self): - """Should handle large object values""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - large_object = { - "items": [ - {"id": i, "name": f"Item {i}", "data": f"Data for item {i}"} - for i in range(1000) - ] - } - await kv.set("large-object", large_object) - value = await kv.get("large-object") - assert value == large_object - await db.close() - - -@pytest.mark.asyncio -class TestKvStorePersistence: - """KvStore persistence tests""" - - async def test_persist_across_instances(self): - """Should persist data across KvStore instances""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - kv = await KvStore.from_database(db) - - await kv.set("persist-key", "persist-value") - - # Create new KvStore instance with same database - new_kv = await KvStore.from_database(db) - value = await new_kv.get("persist-key") - assert value == "persist-value" - - await db.close() diff --git a/sdk/python/tests/test_toolcalls.py b/sdk/python/tests/test_toolcalls.py deleted file mode 100644 index 3f1bb944..00000000 --- a/sdk/python/tests/test_toolcalls.py +++ /dev/null @@ -1,474 +0,0 @@ -"""ToolCalls Integration Tests""" - -import asyncio -import os -import tempfile -import time - -import pytest -from turso.aio import connect - -from agentfs_sdk import ToolCalls - - -@pytest.mark.asyncio -class TestToolCallsBasicOperations: - """Basic ToolCalls operations""" - - async def test_start_tool_call_returns_id(self): - """Should start a tool call and return an ID""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - call_id = await tools.start("test_tool", {"arg1": "value1"}) - assert call_id > 0 - await db.close() - - async def test_start_without_parameters(self): - """Should start a tool call without parameters""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - call_id = await tools.start("simple_tool") - assert call_id > 0 - - tool_call = await tools.get(call_id) - assert tool_call is not None - assert tool_call.name == "simple_tool" - assert tool_call.parameters is None - assert tool_call.status == "pending" - await db.close() - - async def test_mark_call_as_successful(self): - """Should mark a tool call as successful""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - call_id = await tools.start("test_tool", {"input": "test"}) - await tools.success(call_id, {"output": "result"}) - - tool_call = await tools.get(call_id) - assert tool_call is not None - assert tool_call.status == "success" - assert tool_call.result == {"output": "result"} - assert tool_call.completed_at is not None and tool_call.completed_at > 0 - assert tool_call.duration_ms is not None and tool_call.duration_ms >= 0 - await db.close() - - async def test_mark_successful_without_result(self): - """Should mark a tool call as successful without result""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - call_id = await tools.start("test_tool", {"input": "test"}) - await tools.success(call_id) - - tool_call = await tools.get(call_id) - assert tool_call is not None - assert tool_call.status == "success" - assert tool_call.result is None - await db.close() - - async def test_mark_call_as_failed(self): - """Should mark a tool call as failed""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - call_id = await tools.start("test_tool", {"input": "test"}) - await tools.error(call_id, "Something went wrong") - - tool_call = await tools.get(call_id) - assert tool_call is not None - assert tool_call.status == "error" - assert tool_call.error == "Something went wrong" - assert tool_call.completed_at is not None and tool_call.completed_at > 0 - assert tool_call.duration_ms is not None and tool_call.duration_ms >= 0 - await db.close() - - async def test_get_tool_call_by_id(self): - """Should get a tool call by ID""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - call_id = await tools.start("test_tool", {"arg": "value"}) - tool_call = await tools.get(call_id) - - assert tool_call is not None - assert tool_call.id == call_id - assert tool_call.name == "test_tool" - assert tool_call.parameters == {"arg": "value"} - assert tool_call.status == "pending" - assert tool_call.started_at > 0 - await db.close() - - async def test_get_nonexistent_id(self): - """Should return None for non-existent ID""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - tool_call = await tools.get(99999) - assert tool_call is None - await db.close() - - -@pytest.mark.asyncio -class TestToolCallsQueryOperations: - """ToolCalls query operations""" - - async def test_get_by_name(self): - """Should get tool calls by name""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - await tools.start("tool_a", {"test": 1}) - await tools.start("tool_b", {"test": 2}) - await tools.start("tool_a", {"test": 3}) - - results = await tools.get_by_name("tool_a") - assert len(results) == 2 - assert all(tc.name == "tool_a" for tc in results) - await db.close() - - async def test_limit_results_by_name(self): - """Should limit results when querying by name""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - await tools.start("tool_a", {"test": 1}) - await tools.start("tool_a", {"test": 2}) - await tools.start("tool_a", {"test": 3}) - - results = await tools.get_by_name("tool_a", limit=2) - assert len(results) == 2 - await db.close() - - async def test_get_recent_calls(self): - """Should get recent tool calls""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - await tools.start("tool_old") - # Wait to ensure different timestamps - await asyncio.sleep(1.1) - midpoint = int(time.time()) - await asyncio.sleep(1.1) - await tools.start("tool_new") - - results = await tools.get_recent(midpoint) - assert len(results) >= 1 - assert all(tc.started_at >= midpoint for tc in results) - await db.close() - - async def test_limit_recent_calls(self): - """Should limit recent tool calls""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - now = int(time.time()) - - await tools.start("tool_1") - await tools.start("tool_2") - await tools.start("tool_3") - - results = await tools.get_recent(now - 10, limit=2) - assert len(results) <= 2 - await db.close() - - async def test_empty_results_for_nonexistent_name(self): - """Should return empty array when no matching tool calls by name""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - results = await tools.get_by_name("non_existent_tool") - assert results == [] - await db.close() - - -@pytest.mark.asyncio -class TestToolCallsStatistics: - """ToolCalls statistics""" - - async def test_calculate_statistics(self): - """Should calculate tool call statistics""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - # Create some tool calls - id1 = await tools.start("tool_a", {"test": 1}) - await tools.success(id1, {"result": "ok"}) - - id2 = await tools.start("tool_a", {"test": 2}) - await tools.error(id2, "failed") - - id3 = await tools.start("tool_a", {"test": 3}) - await tools.success(id3, {"result": "ok"}) - - id4 = await tools.start("tool_b", {"test": 4}) - await tools.success(id4, {"result": "ok"}) - - stats = await tools.get_stats() - - assert len(stats) == 2 - - tool_a_stats = next((s for s in stats if s.name == "tool_a"), None) - assert tool_a_stats is not None - assert tool_a_stats.total_calls == 3 - assert tool_a_stats.successful == 2 - assert tool_a_stats.failed == 1 - assert tool_a_stats.avg_duration_ms >= 0 - - tool_b_stats = next((s for s in stats if s.name == "tool_b"), None) - assert tool_b_stats is not None - assert tool_b_stats.total_calls == 1 - assert tool_b_stats.successful == 1 - assert tool_b_stats.failed == 0 - await db.close() - - async def test_exclude_pending_from_stats(self): - """Should exclude pending calls from statistics""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - id1 = await tools.start("tool_a", {"test": 1}) - await tools.success(id1, {"result": "ok"}) - - # This one stays pending - await tools.start("tool_a", {"test": 2}) - - stats = await tools.get_stats() - tool_a_stats = next((s for s in stats if s.name == "tool_a"), None) - - assert tool_a_stats is not None - assert tool_a_stats.total_calls == 1 # Only completed calls - await db.close() - - async def test_empty_stats_no_completed_calls(self): - """Should return empty array when no completed calls""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - await tools.start("tool_a", {"test": 1}) - stats = await tools.get_stats() - assert stats == [] - await db.close() - - -@pytest.mark.asyncio -class TestToolCallsComplexData: - """ToolCalls complex parameters and results""" - - async def test_complex_nested_parameters(self): - """Should handle complex nested parameters""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - complex_params = { - "user": {"id": 123, "name": "Test User"}, - "options": {"timeout": 5000, "retry": True}, - "data": [1, 2, 3, 4, 5], - } - - call_id = await tools.start("complex_tool", complex_params) - tool_call = await tools.get(call_id) - - assert tool_call is not None - assert tool_call.parameters == complex_params - await db.close() - - async def test_complex_nested_results(self): - """Should handle complex nested results""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - complex_result = { - "data": {"items": [{"id": 1, "value": "a"}, {"id": 2, "value": "b"}]}, - "metadata": {"count": 2, "hasMore": False}, - } - - call_id = await tools.start("complex_tool") - await tools.success(call_id, complex_result) - tool_call = await tools.get(call_id) - - assert tool_call is not None - assert tool_call.result == complex_result - await db.close() - - async def test_large_parameters(self): - """Should handle large parameters""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - large_params = {"items": [{"id": i, "data": f"Data for item {i}"} for i in range(100)]} - - call_id = await tools.start("large_tool", large_params) - tool_call = await tools.get(call_id) - - assert tool_call is not None - assert tool_call.parameters == large_params - await db.close() - - -@pytest.mark.asyncio -class TestToolCallsPersistence: - """ToolCalls persistence""" - - async def test_persist_across_instances(self): - """Should persist tool calls across instances""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - call_id = await tools.start("persist_tool", {"test": "value"}) - await tools.success(call_id, {"result": "ok"}) - - # Create new ToolCalls instance with same database - new_tools = await ToolCalls.from_database(db) - tool_call = await new_tools.get(call_id) - - assert tool_call is not None - assert tool_call.name == "persist_tool" - assert tool_call.status == "success" - await db.close() - - -@pytest.mark.asyncio -class TestToolCallsOrdering: - """ToolCalls ordering""" - - async def test_order_by_started_at_desc(self): - """Should return tool calls ordered by started_at desc""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - await tools.start("tool_first") - await asyncio.sleep(0.1) - await tools.start("tool_second") - await asyncio.sleep(0.1) - await tools.start("tool_third") - - recent = await tools.get_recent(0) - - assert len(recent) >= 3 - # Most recent first - for i in range(len(recent) - 1): - assert recent[i].started_at >= recent[i + 1].started_at - await db.close() - - -@pytest.mark.asyncio -class TestToolCallsRecord: - """ToolCalls record method""" - - async def test_record_completed_call(self): - """Should record a completed tool call""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - start = int(time.time()) - end = start + 1 - - call_id = await tools.record( - "test_tool", - start, - end, - parameters={"param": "value"}, - result={"result": "success"}, - ) - - assert call_id > 0 - - # Get the tool call - call = await tools.get(call_id) - assert call is not None - assert call.name == "test_tool" - assert call.parameters == {"param": "value"} - assert call.result == {"result": "success"} - assert call.status == "success" - await db.close() - - async def test_record_failed_call(self): - """Should record a failed tool call""" - with tempfile.TemporaryDirectory() as tmpdir: - db_path = os.path.join(tmpdir, "test.db") - db = await connect(db_path) - await db.execute("PRAGMA unstable_capture_data_changes_conn('full')") - tools = await ToolCalls.from_database(db) - - start = int(time.time()) - end = start + 1 - - call_id = await tools.record( - "test_tool", start, end, parameters={"param": "value"}, error="Failed" - ) - - assert call_id > 0 - - # Get the tool call - call = await tools.get(call_id) - assert call is not None - assert call.name == "test_tool" - assert call.error == "Failed" - assert call.status == "error" - await db.close() diff --git a/sdk/python/uv.lock b/sdk/python/uv.lock deleted file mode 100644 index 70dd611b..00000000 --- a/sdk/python/uv.lock +++ /dev/null @@ -1,259 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.10" - -[[package]] -name = "agentfs-sdk" -version = "0.6.4" -source = { editable = "." } -dependencies = [ - { name = "pyturso" }, -] - -[package.dev-dependencies] -dev = [ - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "ruff" }, - { name = "ty" }, -] - -[package.metadata] -requires-dist = [{ name = "pyturso", specifier = "==0.4.4" }] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=9.0.0" }, - { name = "pytest-asyncio", specifier = ">=1.3.0" }, - { name = "ruff", specifier = ">=0.14.0" }, - { name = "ty", specifier = ">=0.0.1a34" }, -] - -[[package]] -name = "backports-asyncio-runner" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - -[[package]] -name = "packaging" -version = "25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, -] - -[[package]] -name = "pytest" -version = "9.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, -] - -[[package]] -name = "pytest-asyncio" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, - { name = "pytest" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, -] - -[[package]] -name = "pyturso" -version = "0.4.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/26/11/eb759eaa18a7b19e6a7321c3fff7facc4fdaccda52088cfe957c8ff81cd1/pyturso-0.4.4.tar.gz", hash = "sha256:591ecfa37840801715d4faabae3a082b4774b7a7efe7860a32db92de2a5a8878", size = 1451696, upload-time = "2026-01-30T10:40:48.235Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/95/670d6daf25b3a3268ba5fb40454600410084a2b94173eb0ece3bc70760aa/pyturso-0.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ca174fefb7d550acbe08ac1bb5947cdc56a16837d813a7ade73f3ab9e5f826", size = 3986906, upload-time = "2026-01-30T10:40:09.275Z" }, - { url = "https://files.pythonhosted.org/packages/de/a0/eb10eeb1a8e7be84da80549c34b57a79a39527b8c6778ac12aef509d18a3/pyturso-0.4.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de87caa44d6f464ef7bf2c9860d3013d18d2c6cec142e6fb9917488954d6a53b", size = 13326239, upload-time = "2026-01-30T10:40:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/e5/48/ba10905f9ff1c1b422ab8aa3a8660ffdde17e7d1ea3098d7069fd2851ca9/pyturso-0.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ab95383dea66684536d23f25c132aabd65bbfc38edac40b03e9ea6327579db7", size = 3987523, upload-time = "2026-01-30T10:40:15.62Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b6/212a0b83289b7a4d195a3809322ce6328547fc6d821d648ed24a2a45303b/pyturso-0.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f18b8e4666e81cf0711c00a331c696928e532704ecd837bc01376cd94f34df0", size = 13326608, upload-time = "2026-01-30T10:40:19.481Z" }, - { url = "https://files.pythonhosted.org/packages/23/c3/a444a1b9ff02b7314f7c2f3a5ca7992e6e93c63258721c17b99c763c4b86/pyturso-0.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02b10dd0b018d7ba9ae3c6295774a423949193a1c16dc500472ab016665fdf54", size = 3985600, upload-time = "2026-01-30T10:40:22.775Z" }, - { url = "https://files.pythonhosted.org/packages/fe/b9/8530ada45e3cc1b32ade08ecc18ed4ffb3598c82079c56a5d7b1bb2c967e/pyturso-0.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7cc9aec8ddb41882f17afaf65c17f3324f89f05e3d61e12b3a66bef35c4a9d1", size = 13331678, upload-time = "2026-01-30T10:40:25.381Z" }, - { url = "https://files.pythonhosted.org/packages/12/2e/06e10efa9fc46fa8c867f0dc0c25b4164642a72dc184465522153b067592/pyturso-0.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab1793fbbd95b29ccecb680d122bc4726b674c8ec8232d4183eeb662a388e49c", size = 3985399, upload-time = "2026-01-30T10:40:28.785Z" }, - { url = "https://files.pythonhosted.org/packages/c3/7c/17fb05bbef22f558cf7350177312abde2b8c84bd631a88bb4e92b2eac3dc/pyturso-0.4.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9763528f126c85aabc41eab89bb06ea46f570bf9a377dd842a8d85ee46e4b2", size = 13330169, upload-time = "2026-01-30T10:40:32.076Z" }, - { url = "https://files.pythonhosted.org/packages/cf/09/00226f913e9627af87a429da27947147971120957c8469971e06d950aa41/pyturso-0.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:59166bda2bb0e584aa3d03e44a52fddbe0bafb48713daf5ad6b00ad04c2778c9", size = 3985743, upload-time = "2026-01-30T10:40:35.516Z" }, - { url = "https://files.pythonhosted.org/packages/73/17/24596a78031f1a4f0dd1dd8de8d46263eeaf89c66091ec7b3de47b428ed4/pyturso-0.4.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ab4cdd3faf8e0daa53554cfb1c25ccb59b15688f0097871457df8ecd0fa5cb9", size = 13323704, upload-time = "2026-01-30T10:40:38.712Z" }, - { url = "https://files.pythonhosted.org/packages/57/b3/fd0d583f7990dbfa4a6ef1bdbb5e52f286e4dc69daf6e37a9ec9f19d8d04/pyturso-0.4.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea4ca6c12fa6478e0b0de11a30e38b35f6faed4269dc8e37d77cb4d8629ef5e8", size = 13323257, upload-time = "2026-01-30T10:40:45.547Z" }, -] - -[[package]] -name = "ruff" -version = "0.14.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/1b/ab712a9d5044435be8e9a2beb17cbfa4c241aa9b5e4413febac2a8b79ef2/ruff-0.14.9.tar.gz", hash = "sha256:35f85b25dd586381c0cc053f48826109384c81c00ad7ef1bd977bfcc28119d5b", size = 5809165, upload-time = "2025-12-11T21:39:47.381Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/1c/d1b1bba22cffec02351c78ab9ed4f7d7391876e12720298448b29b7229c1/ruff-0.14.9-py3-none-linux_armv6l.whl", hash = "sha256:f1ec5de1ce150ca6e43691f4a9ef5c04574ad9ca35c8b3b0e18877314aba7e75", size = 13576541, upload-time = "2025-12-11T21:39:14.806Z" }, - { url = "https://files.pythonhosted.org/packages/94/ab/ffe580e6ea1fca67f6337b0af59fc7e683344a43642d2d55d251ff83ceae/ruff-0.14.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ed9d7417a299fc6030b4f26333bf1117ed82a61ea91238558c0268c14e00d0c2", size = 13779363, upload-time = "2025-12-11T21:39:20.29Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f8/2be49047f929d6965401855461e697ab185e1a6a683d914c5c19c7962d9e/ruff-0.14.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d5dc3473c3f0e4a1008d0ef1d75cee24a48e254c8bed3a7afdd2b4392657ed2c", size = 12925292, upload-time = "2025-12-11T21:39:38.757Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e9/08840ff5127916bb989c86f18924fd568938b06f58b60e206176f327c0fe/ruff-0.14.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84bf7c698fc8f3cb8278830fb6b5a47f9bcc1ed8cb4f689b9dd02698fa840697", size = 13362894, upload-time = "2025-12-11T21:39:02.524Z" }, - { url = "https://files.pythonhosted.org/packages/31/1c/5b4e8e7750613ef43390bb58658eaf1d862c0cc3352d139cd718a2cea164/ruff-0.14.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa733093d1f9d88a5d98988d8834ef5d6f9828d03743bf5e338bf980a19fce27", size = 13311482, upload-time = "2025-12-11T21:39:17.51Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3a/459dce7a8cb35ba1ea3e9c88f19077667a7977234f3b5ab197fad240b404/ruff-0.14.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a1cfb04eda979b20c8c19550c8b5f498df64ff8da151283311ce3199e8b3648", size = 14016100, upload-time = "2025-12-11T21:39:41.948Z" }, - { url = "https://files.pythonhosted.org/packages/a6/31/f064f4ec32524f9956a0890fc6a944e5cf06c63c554e39957d208c0ffc45/ruff-0.14.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1e5cb521e5ccf0008bd74d5595a4580313844a42b9103b7388eca5a12c970743", size = 15477729, upload-time = "2025-12-11T21:39:23.279Z" }, - { url = "https://files.pythonhosted.org/packages/7a/6d/f364252aad36ccd443494bc5f02e41bf677f964b58902a17c0b16c53d890/ruff-0.14.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd429a8926be6bba4befa8cdcf3f4dd2591c413ea5066b1e99155ed245ae42bb", size = 15122386, upload-time = "2025-12-11T21:39:33.125Z" }, - { url = "https://files.pythonhosted.org/packages/20/02/e848787912d16209aba2799a4d5a1775660b6a3d0ab3944a4ccc13e64a02/ruff-0.14.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab208c1b7a492e37caeaf290b1378148f75e13c2225af5d44628b95fd7834273", size = 14497124, upload-time = "2025-12-11T21:38:59.33Z" }, - { url = "https://files.pythonhosted.org/packages/f3/51/0489a6a5595b7760b5dbac0dd82852b510326e7d88d51dbffcd2e07e3ff3/ruff-0.14.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72034534e5b11e8a593f517b2f2f2b273eb68a30978c6a2d40473ad0aaa4cb4a", size = 14195343, upload-time = "2025-12-11T21:39:44.866Z" }, - { url = "https://files.pythonhosted.org/packages/f6/53/3bb8d2fa73e4c2f80acc65213ee0830fa0c49c6479313f7a68a00f39e208/ruff-0.14.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:712ff04f44663f1b90a1195f51525836e3413c8a773574a7b7775554269c30ed", size = 14346425, upload-time = "2025-12-11T21:39:05.927Z" }, - { url = "https://files.pythonhosted.org/packages/ad/04/bdb1d0ab876372da3e983896481760867fc84f969c5c09d428e8f01b557f/ruff-0.14.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a111fee1db6f1d5d5810245295527cda1d367c5aa8f42e0fca9a78ede9b4498b", size = 13258768, upload-time = "2025-12-11T21:39:08.691Z" }, - { url = "https://files.pythonhosted.org/packages/40/d9/8bf8e1e41a311afd2abc8ad12be1b6c6c8b925506d9069b67bb5e9a04af3/ruff-0.14.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8769efc71558fecc25eb295ddec7d1030d41a51e9dcf127cbd63ec517f22d567", size = 13326939, upload-time = "2025-12-11T21:39:53.842Z" }, - { url = "https://files.pythonhosted.org/packages/f4/56/a213fa9edb6dd849f1cfbc236206ead10913693c72a67fb7ddc1833bf95d/ruff-0.14.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:347e3bf16197e8a2de17940cd75fd6491e25c0aa7edf7d61aa03f146a1aa885a", size = 13578888, upload-time = "2025-12-11T21:39:35.988Z" }, - { url = "https://files.pythonhosted.org/packages/33/09/6a4a67ffa4abae6bf44c972a4521337ffce9cbc7808faadede754ef7a79c/ruff-0.14.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7715d14e5bccf5b660f54516558aa94781d3eb0838f8e706fb60e3ff6eff03a8", size = 14314473, upload-time = "2025-12-11T21:39:50.78Z" }, - { url = "https://files.pythonhosted.org/packages/12/0d/15cc82da5d83f27a3c6b04f3a232d61bc8c50d38a6cd8da79228e5f8b8d6/ruff-0.14.9-py3-none-win32.whl", hash = "sha256:df0937f30aaabe83da172adaf8937003ff28172f59ca9f17883b4213783df197", size = 13202651, upload-time = "2025-12-11T21:39:26.628Z" }, - { url = "https://files.pythonhosted.org/packages/32/f7/c78b060388eefe0304d9d42e68fab8cffd049128ec466456cef9b8d4f06f/ruff-0.14.9-py3-none-win_amd64.whl", hash = "sha256:c0b53a10e61df15a42ed711ec0bda0c582039cf6c754c49c020084c55b5b0bc2", size = 14702079, upload-time = "2025-12-11T21:39:11.954Z" }, - { url = "https://files.pythonhosted.org/packages/26/09/7a9520315decd2334afa65ed258fed438f070e31f05a2e43dd480a5e5911/ruff-0.14.9-py3-none-win_arm64.whl", hash = "sha256:8e821c366517a074046d92f0e9213ed1c13dbc5b37a7fc20b07f79b64d62cc84", size = 13744730, upload-time = "2025-12-11T21:39:29.659Z" }, -] - -[[package]] -name = "tomli" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, - { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, - { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, - { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, - { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, - { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, - { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, - { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, - { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, - { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, - { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, - { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, - { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, - { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, - { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, - { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, - { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, - { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, - { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, -] - -[[package]] -name = "ty" -version = "0.0.1a34" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/f9/f467d2fbf02a37af5d779eb21c59c7d5c9ce8c48f620d590d361f5220208/ty-0.0.1a34.tar.gz", hash = "sha256:659e409cc3b5c9fb99a453d256402a4e3bd95b1dbcc477b55c039697c807ab79", size = 4735988, upload-time = "2025-12-12T18:29:23.204Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/b7/d5a5c611baaa20e85971a7c9a527aaf3e8fb47e15de88d1db39c64ee3638/ty-0.0.1a34-py3-none-linux_armv6l.whl", hash = "sha256:00c138e28b12a80577ee3e15fc638eb1e35cf5aa75f5967bf2d1893916ce571c", size = 9708675, upload-time = "2025-12-12T18:29:06.571Z" }, - { url = "https://files.pythonhosted.org/packages/cb/62/0b78976c8da58b90a86d1a1b8816ff4a6e8437f6e52bb6800c4483242e7f/ty-0.0.1a34-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cbb9c187164675647143ecb56e684d6766f7d5ba7f6874a369fe7c3d380a6c92", size = 9515760, upload-time = "2025-12-12T18:28:56.901Z" }, - { url = "https://files.pythonhosted.org/packages/39/1f/4e3d286b37aab3428a30b8f5db5533b8ce6e23b1bd84f77a137bd782b418/ty-0.0.1a34-py3-none-macosx_11_0_arm64.whl", hash = "sha256:68b2375b366ee799a896594cde393a1b60414efdfd31399c326bfc136bfc41f3", size = 9064633, upload-time = "2025-12-12T18:29:10.211Z" }, - { url = "https://files.pythonhosted.org/packages/5d/31/e17049b868f5cac7590c000f31ff9453e4360125416da4e8195e82b5409a/ty-0.0.1a34-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f6b68d9673e43bdd5bdcaa6b5db50e873431fc44dde5e25e253e8226ec93ac1", size = 9310295, upload-time = "2025-12-12T18:29:21.635Z" }, - { url = "https://files.pythonhosted.org/packages/77/1d/7a89b3032e84a01223d0c33e47f33eef436ca36949b28600554a2a4da1f8/ty-0.0.1a34-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:832b360fd397c076e294c252db52581b9ecb38d8063d6262ac927610540702be", size = 9498451, upload-time = "2025-12-12T18:29:24.955Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5e/e782c4367d14b965b1ee9bddc3f3102982ff1cc2dae699c201ecd655e389/ty-0.0.1a34-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb6fc497f1feb67e299fd3507ed30498c7e15b31099b3dcdbeca6b7ac2d3129", size = 9912522, upload-time = "2025-12-12T18:29:00.252Z" }, - { url = "https://files.pythonhosted.org/packages/9c/25/4d72d7174b60adeb9df6e4c5d8552161da2b84ddcebed8ab37d0f7f266ab/ty-0.0.1a34-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:284c8cfd64f255d942ef21953e3d40d087c74dec27e16495bd656decdd208f59", size = 10518743, upload-time = "2025-12-12T18:28:54.944Z" }, - { url = "https://files.pythonhosted.org/packages/05/c5/30a6e377bcab7d5b65d5c78740635b23ecee647bf268c9dc82a91d41c9ba/ty-0.0.1a34-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c34b028305642fd3a9076d4b07d651a819c61a65371ef38cde60f0b54dce6180", size = 10285473, upload-time = "2025-12-12T18:29:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/97/aa/d2cd564ee37a587c8311383a5687584c9aed241a9e67301ee0280301eef3/ty-0.0.1a34-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad997a21648dc64017f11a96b7bb44f088ab0fd589decadc2d686fc97b102f4e", size = 10298873, upload-time = "2025-12-12T18:29:12.38Z" }, - { url = "https://files.pythonhosted.org/packages/2e/80/c427dabd51b5d8b50fc375e18674c098877a9d6545af810ccff4e40ff74a/ty-0.0.1a34-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1afe9798f94c0fbb9e42ff003dfcb4df982f97763d93e5b1d53f9da865a53af", size = 9851399, upload-time = "2025-12-12T18:29:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/cc/d8/7240c0e13bc3405b190b4437fbc67c86aa70e349b282e5fa79282181532b/ty-0.0.1a34-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bd335010aa211fbf8149d3507d6331bdb947d5328ca31388cecdbd2eb49275c3", size = 9261475, upload-time = "2025-12-12T18:29:04.638Z" }, - { url = "https://files.pythonhosted.org/packages/6b/a1/6538f8fe7a5b1a71b20461d905969b7f62574cf9c8c6af580b765a647289/ty-0.0.1a34-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:29ebcc56aabaf6aa85c3baf788e211455ffc9935b807ddc9693954b6990e9a3c", size = 9554878, upload-time = "2025-12-12T18:29:16.349Z" }, - { url = "https://files.pythonhosted.org/packages/3d/f2/b8ab163b928de329d88a5f04a5c399a40c1c099b827c70e569e539f9a755/ty-0.0.1a34-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0cbb5a68fddec83c39db6b5f0a5c5da5a3f7d7620e4bcb4ad5bf3a0c7f89ab45", size = 9651340, upload-time = "2025-12-12T18:29:19.92Z" }, - { url = "https://files.pythonhosted.org/packages/dc/1b/1e4e24b684ee5f22dda18d86846430b123fb2e985f0c0eb986e6eccec1b9/ty-0.0.1a34-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f9b3fd934982a9497237bf39fa472f6d201260ac95b3dc75ba9444d05ec01654", size = 9944488, upload-time = "2025-12-12T18:28:58.544Z" }, - { url = "https://files.pythonhosted.org/packages/80/b0/6435f1795f76c57598933624af58bf67385c96b8fa3252f5f9087173e21a/ty-0.0.1a34-py3-none-win32.whl", hash = "sha256:bdabc3f1a048bc2891d4184b818a7ee855c681dd011d00ee672a05bfe6451156", size = 9151401, upload-time = "2025-12-12T18:28:53.028Z" }, - { url = "https://files.pythonhosted.org/packages/73/2e/adce0d7c07f6de30c7f3c125744ec818c7f04b14622a739fe17d4d0bdb93/ty-0.0.1a34-py3-none-win_amd64.whl", hash = "sha256:a4caa2e58685d6801719becbd0504fe61e3ab94f2509e84759f755a0ca480ada", size = 10031079, upload-time = "2025-12-12T18:29:14.556Z" }, - { url = "https://files.pythonhosted.org/packages/23/0d/1f123c69ce121dcabf5449a456a9a37c3bbad396e9e7484514f1fe568f96/ty-0.0.1a34-py3-none-win_arm64.whl", hash = "sha256:dd02c22b538657b042d154fe2d5e250dfb20c862b32e6036a6ffce2fd1ebca9d", size = 9534879, upload-time = "2025-12-12T18:29:18.187Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml deleted file mode 100644 index a174a3a6..00000000 --- a/sdk/rust/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "agentfs-sdk" -version = "0.6.4" -edition = "2021" -description = "AgentFS SDK for Rust" -license = "MIT" - -[dependencies] -turso = { version = "0.5", features = ["sync"] } -tokio = { version = "1", features = ["full"] } -async-trait = "0.1" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -libc = "0.2" -thiserror = "1.0" -lru = "0.12" -tracing = "0.1" -parking_lot = "0.12.5" - -[target.'cfg(target_os = "macos")'.dependencies] -# `aegis`'s C/NEON backend fails to compile with Apple clang on arm64 due to -# missing SHA3/NEON intrinsics (e.g. `veor3q_u8`). Enabling `pure-rust` makes -# `aegis` skip its C build script entirely on macOS. -aegis = { version = "0.9.6", features = ["pure-rust"] } - -[dev-dependencies] -tempfile = "3" -proptest = "1.4" -criterion = { version = "0.5", features = ["async_tokio"] } -rand = "0.8" - -[[bench]] -name = "overlayfs" -harness = false - -[[bench]] -name = "workload" -harness = false - -[profile.bench] -debug = true diff --git a/sdk/rust/src/filesystem/agentfs.rs b/sdk/rust/src/filesystem/agentfs.rs deleted file mode 100644 index c28c133f..00000000 --- a/sdk/rust/src/filesystem/agentfs.rs +++ /dev/null @@ -1,9338 +0,0 @@ -use crate::error::{Error, Result}; -use async_trait::async_trait; -use lru::LruCache; -use parking_lot::RwLock; -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::num::NonZeroUsize; -use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; -use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use tokio::sync::Mutex as AsyncMutex; -use turso::transaction::{Transaction, TransactionBehavior}; -use turso::{Builder, Connection, Value}; - -use super::{ - BoxedFile, DirEntry, File, FileSystem, FilesystemStats, FsError, Stats, TimeChange, WriteRange, - DEFAULT_DIR_MODE, DEFAULT_FILE_MODE, MAX_NAME_LEN, S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, -}; -use crate::connection_pool::{ConnectionPool, ConnectionPoolOptions}; -use crate::schema::{self, AGENTFS_SCHEMA_VERSION}; - -const ROOT_INO: i64 = 1; -const DEFAULT_CHUNK_SIZE: usize = 65536; -/// Tier Three Axis I: raised from 4 KiB to 16 KiB so the (4, 16] KiB tail of -/// codex working-tree files (which dominate at ~14 KiB median) avoids the -/// chunked-storage path entirely. The fs_inode metadata SELECTs in -/// `getattr`/`lookup` explicitly project named columns and do NOT pull -/// `data_inline`, so the only cost of a larger inline blob is paid on actual -/// reads of those specific files — which is more than offset by skipping the -/// extra `fs_data` row and its SELECT+UPDATE-on-write cycle. The persisted -/// `fs_config.inline_threshold` is per-DB so existing databases keep their -/// 4 KiB threshold; only newly-initialised databases pick up the new default. -const DEFAULT_INLINE_THRESHOLD: usize = 16384; -const STORAGE_CHUNKED: i64 = 0; -const STORAGE_INLINE: i64 = 1; -const DENTRY_CACHE_MAX_SIZE: usize = 10000; -const NEGATIVE_DENTRY_CACHE_MAX_SIZE: usize = 10000; -const FILE_BACKED_MAX_CONNECTIONS: usize = 8; -const BUSY_TIMEOUT_SQL: &str = "PRAGMA busy_timeout = 5000"; -const WAL_MODE_SQL: &str = "PRAGMA journal_mode = WAL"; -const BASELINE_SYNCHRONOUS_SQL: &str = "PRAGMA synchronous = NORMAL"; -const DURABLE_SYNCHRONOUS_SQL: &str = "PRAGMA synchronous = FULL"; -const WAL_CHECKPOINT_SQL: &str = "PRAGMA wal_checkpoint(TRUNCATE)"; -const FILE_BACKED_SETUP_SQL: &[&str] = &[BUSY_TIMEOUT_SQL, WAL_MODE_SQL, BASELINE_SYNCHRONOUS_SQL]; -const ATTR_CACHE_MAX_SIZE: usize = 10000; -const WRITE_BATCHER_ENABLE_ENV: &str = "AGENTFS_FUSE_WRITEBACK"; -const WRITE_BATCHER_MS_ENV: &str = "AGENTFS_BATCH_MS"; -const WRITE_BATCHER_BYTES_ENV: &str = "AGENTFS_BATCH_BYTES"; -/// Global (cross-inode) ceiling on in-memory pending write bytes. When the sum -/// of all pending inode batches reaches this, the enqueue path triggers a full -/// batched drain. This bounds memory so the group-commit window -/// (`AGENTFS_BATCH_MS`) can coalesce many file closes into far fewer commits -/// without risking unbounded RSS during a write burst (e.g. git clone). -const WRITE_BATCHER_GLOBAL_BYTES_ENV: &str = "AGENTFS_BATCH_GLOBAL_BYTES"; -/// Per-transaction bounds for the coalescing drain scheduler: one batched -/// drain transaction commits at most this many inodes / pending bytes. When a -/// pass over the pending map exceeds the bound, the remainder is committed in -/// immediately-following back-to-back transactions instead of one unbounded -/// `BEGIN IMMEDIATE`. -const WRITE_BATCHER_TXN_INODES_ENV: &str = "AGENTFS_BATCH_TXN_INODES"; -const WRITE_BATCHER_TXN_BYTES_ENV: &str = "AGENTFS_BATCH_TXN_BYTES"; -const DEFAULT_WRITE_BATCH_MS: u64 = 5; -const DEFAULT_WRITE_BATCH_BYTES: usize = 4 * 1024 * 1024; -const DEFAULT_WRITE_BATCH_GLOBAL_BYTES: usize = 64 * 1024 * 1024; -const DEFAULT_WRITE_BATCH_TXN_INODES: usize = 1024; -const DEFAULT_WRITE_BATCH_TXN_BYTES: usize = 32 * 1024 * 1024; -/// Tier 4 escape hatch. When `AGENTFS_OVERLAY_READS=0`, the SDK reverts to -/// Tier 3 semantics: `pwrite` drains before commit, `pread` drains before -/// read, `merge_pending_view` is a no-op. Defaults to ON so a clean install -/// gets Tier 4 benefits, but operators can flip it OFF without rebuilding if -/// a previously-unknown read-merge bug surfaces in production. -const OVERLAY_READS_ENV: &str = "AGENTFS_OVERLAY_READS"; - -/// Production connection-pool options for local file-backed AgentFS databases. -pub(crate) fn file_backed_connection_pool_options() -> ConnectionPoolOptions { - ConnectionPoolOptions { - max_connections: FILE_BACKED_MAX_CONNECTIONS, - ..ConnectionPoolOptions::default().with_setup_sql(FILE_BACKED_SETUP_SQL.iter().copied()) - } -} - -async fn checkpoint_wal(conn: &Connection) -> Result<()> { - let started = if crate::profiling::is_enabled() { - Some(Instant::now()) - } else { - None - }; - let mut rows = conn.query(WAL_CHECKPOINT_SQL, ()).await?; - while rows.next().await?.is_some() {} - if let Some(started) = started { - crate::profiling::record_wal_checkpoint(started.elapsed()); - } - Ok(()) -} - -fn sqlite_sidecar_path(path: &Path, suffix: &str) -> PathBuf { - PathBuf::from(format!("{}{}", path.display(), suffix)) -} - -fn remove_checkpointed_sidecars(path: &Path) -> Result<()> { - let wal = sqlite_sidecar_path(path, "-wal"); - if let Ok(metadata) = std::fs::metadata(&wal) { - if metadata.len() == 0 { - std::fs::remove_file(&wal)?; - } - } - - let shm = sqlite_sidecar_path(path, "-shm"); - if shm.exists() { - std::fs::remove_file(&shm)?; - } - Ok(()) -} - -/// Returns the value of an env-var boolean flag, falling back to `default` -/// when the variable is unset. Mirrors `env_flag_default` in `cli/src/fuse.rs` -/// so the SDK can agree with the cli on shared env vars (notably -/// `AGENTFS_FUSE_WRITEBACK`, which the cli defaults to TRUE). -fn env_flag_default(name: &str, default: bool) -> bool { - match std::env::var(name) { - Ok(value) => matches!( - value.to_ascii_lowercase().as_str(), - "1" | "true" | "yes" | "on" - ), - Err(_) => default, - } -} - -/// Whether chmod / chown / utimens should force a synchronous batcher drain -/// (one SQLite commit per call) before applying the attribute change. -/// -/// Default TRUE (legacy drain-before-setattr). The deferred path -/// (`AGENTFS_DRAIN_ON_SETATTR=0`) stashes the kernel times via -/// `mark_times_explicit` + `preserve_times` and lets the group commit apply -/// them, which cut clone-phase transactions from ~4,700 to ~250 (clone median -/// -7.4%). It is opt-in because the 2026-05-30 A/B measured a +9.6% workload -/// total regression: deferred commits land after git writes its index, the -/// FUSE adapter's buffered WRITE is enqueued after the SETATTR and clears the -/// stashed mtime/ctime, and the resulting stat drift makes checkout/status -/// re-read ~4,700 files. Flip the default only after deferred commits are -/// attribute-transparent and that A/B goes green. -fn drain_on_setattr() -> bool { - static DRAIN_ON_SETATTR: std::sync::OnceLock = std::sync::OnceLock::new(); - *DRAIN_ON_SETATTR.get_or_init(|| env_flag_default("AGENTFS_DRAIN_ON_SETATTR", true)) -} - -/// Whether DB-backed regular files may keep the kernel page cache across -/// read-only opens (`FOPEN_KEEP_CACHE`). Default true; the FUSE adapter's -/// fingerprint guard revalidates stats at each open. -/// Whether DB-backed (delta) files may grant `FOPEN_KEEP_CACHE` on read-only -/// opens. Public so FUSE adapters can gate their own cached-stats fast path -/// on the same kill switch (`AGENTFS_KEEPCACHE_DELTA=0`). -pub fn keepcache_delta_enabled() -> bool { - static KEEPCACHE_DELTA: std::sync::OnceLock = std::sync::OnceLock::new(); - *KEEPCACHE_DELTA.get_or_init(|| env_flag_default("AGENTFS_KEEPCACHE_DELTA", true)) -} - -fn env_duration_millis(name: &str, default_ms: u64) -> Duration { - std::env::var(name) - .ok() - .and_then(|value| value.parse::().ok()) - .map(Duration::from_millis) - .unwrap_or_else(|| Duration::from_millis(default_ms)) -} - -fn env_usize(name: &str, default_value: usize) -> usize { - std::env::var(name) - .ok() - .and_then(|value| value.parse::().ok()) - .filter(|value| *value > 0) - .unwrap_or(default_value) -} - -/// LRU cache for directory entry lookups. -/// -/// Maps (parent_ino, name) -> child_ino to avoid repeated database queries -/// during path resolution. For a path like `/a/b/c/d`, this reduces queries -/// from 4 to potentially 0 on cache hits. -struct DentryCache { - // Mutex required because LruCache::get() mutates internal order - entries: Mutex>, -} - -impl DentryCache { - fn new(max_size: usize) -> Self { - Self { - entries: Mutex::new(LruCache::new( - NonZeroUsize::new(max_size).expect("cache size must be > 0"), - )), - } - } - - /// Look up a cached entry (updates LRU order) - fn get(&self, parent_ino: i64, name: &str) -> Option { - let entry = self - .entries - .lock() - .unwrap() - .get(&(parent_ino, name.to_string())) - .copied(); - if entry.is_some() { - crate::profiling::record_dentry_cache_hit(); - crate::profiling::record_path_cache_hit(); - } else { - crate::profiling::record_dentry_cache_miss(); - crate::profiling::record_path_cache_miss(); - } - entry - } - - /// Insert an entry into the cache (evicts LRU entry if full) - fn insert(&self, parent_ino: i64, name: &str, child_ino: i64) { - self.entries - .lock() - .unwrap() - .put((parent_ino, name.to_string()), child_ino); - } - - /// Remove an entry from the cache - fn remove(&self, parent_ino: i64, name: &str) { - self.entries - .lock() - .unwrap() - .pop(&(parent_ino, name.to_string())); - } -} - -/// LRU cache for safe negative directory entry lookups. -/// -/// A negative entry means "this (parent, name) did not exist in the last -/// serialized AgentFS view". Every namespace mutation invalidates exactly the -/// affected key before the mutation reports success, so cached ENOENT results -/// cannot hide later creates or renames made through this filesystem. -struct NegativeDentryCache { - entries: Mutex>, -} - -impl NegativeDentryCache { - fn new(max_size: usize) -> Self { - Self { - entries: Mutex::new(LruCache::new( - NonZeroUsize::new(max_size).expect("cache size must be > 0"), - )), - } - } - - fn contains(&self, parent_ino: i64, name: &str) -> bool { - let cached = self - .entries - .lock() - .unwrap() - .get(&(parent_ino, name.to_string())) - .is_some(); - if cached { - crate::profiling::record_negative_cache_hit(); - } else { - crate::profiling::record_negative_cache_miss(); - } - cached - } - - fn insert(&self, parent_ino: i64, name: &str) { - self.entries - .lock() - .unwrap() - .put((parent_ino, name.to_string()), ()); - } - - fn remove(&self, parent_ino: i64, name: &str) { - if self - .entries - .lock() - .unwrap() - .pop(&(parent_ino, name.to_string())) - .is_some() - { - crate::profiling::record_negative_cache_invalidation(); - } - } -} - -/// LRU cache for inode attributes. -/// -/// FUSE and SDK stat-heavy read paths often ask for the same inode metadata -/// repeatedly after lookup/readdir_plus. This cache is conservative: every -/// namespace, metadata, or size/content mutation invalidates the affected inode -/// and parent directory entries before the mutation is considered complete. -struct AttrCache { - entries: Mutex>, -} - -impl AttrCache { - fn new(max_size: usize) -> Self { - Self { - entries: Mutex::new(LruCache::new( - NonZeroUsize::new(max_size).expect("cache size must be > 0"), - )), - } - } - - fn get(&self, ino: i64) -> Option { - let stats = self.entries.lock().unwrap().get(&ino).cloned(); - if stats.is_some() { - crate::profiling::record_attr_cache_hit(); - } else { - crate::profiling::record_attr_cache_miss(); - } - stats - } - - fn insert(&self, stats: Stats) { - self.entries.lock().unwrap().put(stats.ino, stats); - } - - fn remove(&self, ino: i64) { - self.entries.lock().unwrap().pop(&ino); - } -} - -#[derive(Debug, Clone, Copy)] -enum AgentFSWriteBatchDrainReason { - Timer, - Bytes, - Explicit, -} - -/// Explicitly-set timestamps stashed by `utimens` while the inode still has -/// pending batched writes. Instead of paying a dedicated SQLite transaction -/// per SETATTR (the FUSE writeback cache sends one per written file during a -/// clone), the values ride along in the pending entry and the batcher applies -/// them inside the SAME drain transaction, right after the data UPDATE — so -/// the explicitly-set times win over the commit-time stamp without an extra -/// per-file transaction. `getattr`/`lookup` overlay these values onto the -/// SQLite row (`merge_pending_view`) so the change is visible immediately. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -struct PendingTimeChange { - /// (secs, nsec) for atime, when explicitly set. - atime: Option<(i64, i64)>, - /// (secs, nsec) for mtime, when explicitly set. - mtime: Option<(i64, i64)>, - /// (secs, nsec) for ctime (always bumped by the setattr that stashed). - ctime: Option<(i64, i64)>, -} - -impl PendingTimeChange { - fn is_empty(&self) -> bool { - self.atime.is_none() && self.mtime.is_none() && self.ctime.is_none() - } - - /// Per-field "newer wins" merge: fields set by `newer` override ours. - fn apply(&mut self, newer: &PendingTimeChange) { - if newer.atime.is_some() { - self.atime = newer.atime; - } - if newer.mtime.is_some() { - self.mtime = newer.mtime; - } - if newer.ctime.is_some() { - self.ctime = newer.ctime; - } - } - - /// Drop the fields a buffered data write would re-stamp (mtime/ctime). A - /// write AFTER the explicit setattr means the file changed again, so the - /// eventual commit must stamp fresh modification times; an explicitly-set - /// atime is unaffected by writes and survives. - fn clear_write_stamped(&mut self) { - self.mtime = None; - self.ctime = None; - } - - /// Overlay the stashed values onto a `Stats` row read from SQLite, so - /// `getattr`/`lookup` surface explicit `utimens` results immediately even - /// though the row UPDATE is deferred to the next batched drain. - fn merge_into(&self, stats: &mut Stats) { - if let Some((secs, nsec)) = self.atime { - stats.atime = secs; - stats.atime_nsec = nsec as u32; - } - if let Some((secs, nsec)) = self.mtime { - stats.mtime = secs; - stats.mtime_nsec = nsec as u32; - } - if let Some((secs, nsec)) = self.ctime { - stats.ctime = secs; - stats.ctime_nsec = nsec as u32; - } - } -} - -/// Build the time-column SET fragments for a batched data-commit UPDATE so -/// stashed explicit times ride the same statement as size/storage/data -/// (one UPDATE per inode instead of two; ~4,700 extra UPDATEs per clone -/// otherwise). Precedence per column: a stashed explicit value wins; without -/// one, mtime/ctime are stamped with the commit time unless `preserve_times` -/// (an explicit setattr landed after the writes and its values must not be -/// clobbered); atime is only ever written explicitly. -fn write_commit_time_sets( - preserve_times: bool, - explicit_times: Option<&PendingTimeChange>, -) -> Result<(Vec<&'static str>, Vec)> { - let explicit_atime = explicit_times.and_then(|t| t.atime); - let explicit_mtime = explicit_times.and_then(|t| t.mtime); - let explicit_ctime = explicit_times.and_then(|t| t.ctime); - let stamp = if !preserve_times && (explicit_mtime.is_none() || explicit_ctime.is_none()) { - Some(current_timestamp()?) - } else { - None - }; - let mut sets = Vec::new(); - let mut values = Vec::new(); - if let Some((secs, nsec)) = explicit_atime { - sets.push("atime = ?"); - values.push(Value::Integer(secs)); - sets.push("atime_nsec = ?"); - values.push(Value::Integer(nsec)); - } - if let Some((secs, nsec)) = explicit_mtime.or(stamp) { - sets.push("mtime = ?"); - values.push(Value::Integer(secs)); - sets.push("mtime_nsec = ?"); - values.push(Value::Integer(nsec)); - } - if let Some((secs, nsec)) = explicit_ctime.or(stamp) { - sets.push("ctime = ?"); - values.push(Value::Integer(secs)); - sets.push("ctime_nsec = ?"); - values.push(Value::Integer(nsec)); - } - Ok((sets, values)) -} - -/// Apply a stashed `PendingTimeChange` to fs_inode using the drain -/// transaction's connection. Used for time-only pending entries (no data -/// ranges to commit, so there is no data UPDATE to fold the times into); -/// runs inside the drain's `BEGIN IMMEDIATE`. A deleted inode simply matches -/// no row (the unlink already won). -async fn apply_pending_times_with_conn( - conn: &Connection, - ino: i64, - times: &PendingTimeChange, -) -> Result<()> { - let mut updates = Vec::new(); - let mut values: Vec = Vec::new(); - if let Some((secs, nsec)) = times.atime { - updates.push("atime = ?"); - values.push(Value::Integer(secs)); - updates.push("atime_nsec = ?"); - values.push(Value::Integer(nsec)); - } - if let Some((secs, nsec)) = times.mtime { - updates.push("mtime = ?"); - values.push(Value::Integer(secs)); - updates.push("mtime_nsec = ?"); - values.push(Value::Integer(nsec)); - } - if let Some((secs, nsec)) = times.ctime { - updates.push("ctime = ?"); - values.push(Value::Integer(secs)); - updates.push("ctime_nsec = ?"); - values.push(Value::Integer(nsec)); - } - if updates.is_empty() { - return Ok(()); - } - values.push(Value::Integer(ino)); - let sql = format!("UPDATE fs_inode SET {} WHERE ino = ?", updates.join(", ")); - conn.execute(&sql, values).await?; - Ok(()) -} - -struct PendingInodeWrites { - ranges: Vec, - pending_bytes: usize, - /// True when an explicit attribute change (chmod / chown / utimens) was - /// applied to fs_inode AFTER the most recent enqueue for this inode. The - /// deferred data commit normally stamps mtime/ctime with the commit time; - /// when this flag is set it must preserve the explicitly-set times instead - /// (the setattr logically happened after the buffered writes). Reset on - /// every enqueue: a write after the setattr should bump the times again. - times_explicit: bool, - /// Explicit `utimens` values waiting to be committed together with the - /// pending data (see `PendingTimeChange`). Cleared field-wise: a new - /// enqueue drops mtime/ctime (write re-stamps them), the drain clears the - /// values it actually committed. - pending_times: Option, -} - -impl PendingInodeWrites { - fn new() -> Self { - Self { - ranges: Vec::new(), - pending_bytes: 0, - times_explicit: false, - pending_times: None, - } - } - - /// True when nothing is left to commit for this inode (no data ranges and - /// no stashed explicit times) — only then may the entry be dropped. - fn is_drained(&self) -> bool { - self.ranges.is_empty() && self.pending_times.is_none() - } - - fn push_ranges(&mut self, ranges: Vec, byte_count: usize) -> Result<()> { - self.pending_bytes = self - .pending_bytes - .checked_add(byte_count) - .ok_or_else(|| Error::Internal("batched write byte count overflow".to_string()))?; - self.times_explicit = false; - if let Some(times) = &mut self.pending_times { - times.clear_write_stamped(); - if times.is_empty() { - self.pending_times = None; - } - } - self.ranges.extend(ranges); - Ok(()) - } -} - -#[derive(Default)] -struct AgentFSWriteBatcherState { - pending: HashMap, - /// Running sum of `pending_bytes` across every inode in `pending`. Kept in - /// lock-step with the map so the enqueue path can enforce a global memory - /// cap in O(1) instead of summing the map on every write. Every site that - /// mutates a `PendingInodeWrites.pending_bytes` or inserts/removes an entry - /// must keep this consistent (see `debug_assert_total`). - total_pending_bytes: usize, - /// True while the single coalescing drain scheduler task is armed (see - /// `run_drain_scheduler`). Set by the enqueue that arms it, cleared by the - /// scheduler itself under this same lock once nothing is pending — so an - /// enqueue either observes it set (the running scheduler will pick the new - /// write up) or arms a fresh scheduler. Pending work is never stranded. - drain_scheduled: bool, -} - -impl AgentFSWriteBatcherState { - #[cfg(debug_assertions)] - fn debug_assert_total(&self) { - let sum: usize = self.pending.values().map(|b| b.pending_bytes).sum(); - debug_assert_eq!( - sum, self.total_pending_bytes, - "batcher total_pending_bytes drifted from sum of pending entries" - ); - } - - #[cfg(not(debug_assertions))] - #[inline] - fn debug_assert_total(&self) {} -} - -/// In-memory write group-commit queue for FUSE writeback mode. -/// -/// The batcher stores only transient `WriteRange` values and drains them into -/// the canonical SQLite tables. It never creates sidecars and normal durability -/// boundaries (`flush`, `fsync`, `release`, `destroy`) explicitly drain it. -struct AgentFSWriteBatcher { - pool: ConnectionPool, - chunk_size: usize, - inline_threshold: usize, - attr_cache: Arc, - batch_ms: Duration, - batch_bytes: usize, - batch_global_bytes: usize, - /// Per-transaction inode-count bound for batched drains - /// (`AGENTFS_BATCH_TXN_INODES`). See `drain_pending_batched`. - txn_max_inodes: usize, - /// Per-transaction pending-bytes bound for batched drains - /// (`AGENTFS_BATCH_TXN_BYTES`). See `drain_pending_batched`. - txn_max_bytes: usize, - /// Tier 4 mitigation: parking_lot `RwLock` so `peek_pending` / - /// `peek_pending_max_end` can acquire read-only access without contending - /// with writers. The lock is never held across an `.await`, so a sync - /// lock is safe inside async fns. Holding it across an await would block - /// the tokio worker — `take_pending_locked` and friends always extract - /// owned state under the lock and drop the guard before any I/O. - state: RwLock, - commit_lock: AsyncMutex<()>, -} - -impl AgentFSWriteBatcher { - fn from_env( - pool: ConnectionPool, - chunk_size: usize, - inline_threshold: usize, - attr_cache: Arc, - ) -> Self { - Self { - pool, - chunk_size, - inline_threshold, - attr_cache, - batch_ms: env_duration_millis(WRITE_BATCHER_MS_ENV, DEFAULT_WRITE_BATCH_MS), - batch_bytes: env_usize(WRITE_BATCHER_BYTES_ENV, DEFAULT_WRITE_BATCH_BYTES), - batch_global_bytes: env_usize( - WRITE_BATCHER_GLOBAL_BYTES_ENV, - DEFAULT_WRITE_BATCH_GLOBAL_BYTES, - ), - txn_max_inodes: env_usize(WRITE_BATCHER_TXN_INODES_ENV, DEFAULT_WRITE_BATCH_TXN_INODES) - .max(1), - txn_max_bytes: env_usize(WRITE_BATCHER_TXN_BYTES_ENV, DEFAULT_WRITE_BATCH_TXN_BYTES) - .max(1), - state: RwLock::new(AgentFSWriteBatcherState::default()), - commit_lock: AsyncMutex::new(()), - } - } - - async fn enqueue(self: &Arc, ino: i64, ranges: Vec) -> Result<()> { - let ranges: Vec<_> = ranges - .into_iter() - .filter(|range| !range.data.is_empty()) - .collect(); - if ranges.is_empty() { - return Ok(()); - } - - let byte_count = ranges.iter().try_fold(0usize, |acc, range| { - acc.checked_add(range.data.len()) - .ok_or_else(|| Error::Internal("batched write byte count overflow".to_string())) - })?; - let drain_now; - let mut drain_all_now = false; - let mut schedule_drain = false; - - { - let mut state = self.state.write(); - drain_now = { - let entry = state - .pending - .entry(ino) - .or_insert_with(PendingInodeWrites::new); - entry.push_ranges(ranges, byte_count)?; - crate::profiling::record_agentfs_batcher_enqueue(); - crate::profiling::record_agentfs_batcher_pending_bytes(entry.pending_bytes as u64); - - entry.pending_bytes >= self.batch_bytes - }; - state.total_pending_bytes = state.total_pending_bytes.saturating_add(byte_count); - // Global memory ceiling: a single full batched drain is cheaper and - // frees more memory than draining just this inode, so it takes - // precedence over the per-inode trigger. - if state.total_pending_bytes >= self.batch_global_bytes { - drain_all_now = true; - } - // Group commit: arm the single coalescing drain scheduler if it is - // not already running, instead of one timer task per inode (which - // degenerated into a storm of small serialized transactions during - // a clone burst). - if !state.drain_scheduled { - state.drain_scheduled = true; - schedule_drain = true; - } - state.debug_assert_total(); - } - - // Tier Four: invalidate the attr cache as soon as a write is queued, - // not just when the batch commits to SQLite. getattr ORs in - // peek_pending_max_end so the size view stays correct, but other - // consumers (mtime/ctime, link count assumptions) must not see a - // cached pre-write attr after a successful pwrite returns. - self.attr_cache.remove(ino); - - if schedule_drain { - self.spawn_drain_scheduler(); - } - - if drain_all_now { - self.drain_all(AgentFSWriteBatchDrainReason::Bytes).await?; - } else if drain_now { - self.drain_inode(ino, AgentFSWriteBatchDrainReason::Bytes) - .await?; - } - - Ok(()) - } - - async fn drain_inode( - self: &Arc, - ino: i64, - reason: AgentFSWriteBatchDrainReason, - ) -> Result<()> { - // Explicit drains (fsync / kill-switch release/forget/setattr paths) - // happen on every file close during git-clone-style workloads when the - // legacy switches are enabled. Each one used to take its own SQLite - // transaction; when many inodes are pending simultaneously, bundling - // them into a single BEGIN IMMEDIATE / COMMIT pair amortises the - // write-lock acquisition across all pending inodes. The Bytes trigger - // keeps its per-inode behaviour (it fires when ONE inode's pending - // exceeds the per-inode cap); group commits are the scheduler's job - // (`run_drain_scheduler`). - if matches!(reason, AgentFSWriteBatchDrainReason::Explicit) { - return self - .drain_pending_batched(reason, Some(ino)) - .await - .map(|_| ()); - } - - let _commit_guard = self.commit_lock.lock().await; - loop { - // Commit-then-remove (see drain_pending_batched): snapshot this - // inode's ranges by cloning WITHOUT removing them, so a concurrent - // reader never observes a gap where the write is in neither the - // overlay nor committed SQLite. - let snapshot = { - let state = self.state.read(); - state - .pending - .get(&ino) - .filter(|batch| !batch.ranges.is_empty()) - .map(|batch| batch.ranges.clone()) - }; - - let Some(ranges) = snapshot else { - self.cleanup_empty_pending(); - return Ok(()); - }; - - match reason { - AgentFSWriteBatchDrainReason::Timer => { - crate::profiling::record_agentfs_batcher_drain_timer(); - } - AgentFSWriteBatchDrainReason::Bytes => { - crate::profiling::record_agentfs_batcher_drain_bytes(); - } - AgentFSWriteBatchDrainReason::Explicit => { - crate::profiling::record_agentfs_batcher_drain_explicit(); - } - } - - // On error the overlay is intact (nothing removed) — retried later. - self.commit_inode_ranges(ino, &ranges).await?; - - self.remove_committed_prefix(&[(ino, ranges.len())]); - // Anything still pending (ranges enqueued during the commit, other - // inodes, stashed times) is the coalescing scheduler's job. - self.ensure_drain_scheduled(); - } - } - - async fn drain_all(self: &Arc, reason: AgentFSWriteBatchDrainReason) -> Result<()> { - // Always batch on full drain: destroy / finalize / public AgentFS::drain_all. - loop { - self.drain_pending_batched(reason, None).await?; - let still_pending = { - let state = self.state.read(); - !state.pending.is_empty() - }; - if !still_pending { - return Ok(()); - } - } - } - - /// Drain currently-pending inode batches inside a single SQLite - /// transaction. Holds one connection and one `BEGIN IMMEDIATE` / `COMMIT` - /// pair across all per-inode chunk writes, instead of paying one - /// transaction per inode like `commit_batch` does. - /// - /// One transaction is bounded by `txn_max_inodes` / `txn_max_bytes` - /// (`AGENTFS_BATCH_TXN_INODES` / `AGENTFS_BATCH_TXN_BYTES`); when the - /// pending map exceeds the bound the call commits a bounded subset and - /// returns `Ok(true)` so the caller immediately drains again - /// (back-to-back transactions) instead of building one unbounded txn. - /// Returns `Ok(false)` when everything that was pending at snapshot time - /// has been committed. - /// - /// `required_ino` lets `drain_inode(_, Explicit)` express its caller - /// contract: "the writes queued for this inode must be durable when this - /// returns". If the inode is not in pending when we take the snapshot, it - /// was committed by a concurrent drain and the contract is already met. - /// If it IS pending, it is always selected into this transaction - /// regardless of the per-transaction bounds. - async fn drain_pending_batched( - self: &Arc, - reason: AgentFSWriteBatchDrainReason, - required_ino: Option, - ) -> Result { - let _commit_guard = self.commit_lock.lock().await; - - // Tier 4 corruption fix (commit-then-remove): SNAPSHOT pending ranges - // by cloning, WITHOUT removing them from the overlay. `pread`/`getattr` - // consult the overlay and then SQLite with no lock spanning the two; if - // we removed the ranges here (as the original `mem::take` did), a read - // landing between the take and `txn.commit` would find the write in - // NEITHER the overlay nor committed SQLite and return stale data - // (the intermittent git-clone corruption). Leaving the overlay - // populated until after the commit guarantees every write is always - // visible in the overlay OR in SQLite. - let (snapshot, more_pending): (Vec<(i64, Vec)>, bool) = { - let state = self.state.read(); - let mut selected: Vec<(i64, Vec)> = Vec::new(); - let mut selected_bytes = 0usize; - let mut truncated = false; - // The explicit-drain contract inode is always part of this - // transaction, independent of the bounds. - if let Some(req) = required_ino { - if let Some(batch) = state.pending.get(&req) { - if !batch.ranges.is_empty() || batch.pending_times.is_some() { - selected_bytes = selected_bytes.saturating_add(batch.pending_bytes); - selected.push((req, batch.ranges.clone())); - } - } - } - for (ino, batch) in state.pending.iter() { - if Some(*ino) == required_ino { - continue; - } - if batch.ranges.is_empty() && batch.pending_times.is_none() { - continue; - } - // Bound the transaction by inode count and pending bytes; the - // first selected inode is always admitted so progress is - // guaranteed even when a single inode exceeds the byte bound. - if selected.len() >= self.txn_max_inodes - || (!selected.is_empty() - && selected_bytes.saturating_add(batch.pending_bytes) > self.txn_max_bytes) - { - truncated = true; - break; - } - selected_bytes = selected_bytes.saturating_add(batch.pending_bytes); - selected.push((*ino, batch.ranges.clone())); - } - (selected, truncated) - }; - - if snapshot.is_empty() { - self.cleanup_empty_pending(); - return Ok(false); - } - - // (ino, committed_raw_range_count, normalized ranges to write). - // Entries with an empty range list are still included: they carry - // stashed explicit times (`pending_times`) that must be committed in - // this transaction even though there is no data to write. - let mut to_commit: Vec<(i64, usize, Vec)> = - Vec::with_capacity(snapshot.len()); - for (ino, ranges) in &snapshot { - let range_refs: Vec<_> = ranges - .iter() - .map(|range| WriteRangeRef { - offset: range.offset, - data: range.data.as_slice(), - }) - .collect(); - // On normalize error the overlay is left intact (nothing removed), - // so the ranges are simply retried on the next drain. - let normalized = normalize_write_ranges(&range_refs)?; - if !normalized.is_empty() { - crate::profiling::record_agentfs_batcher_coalesced_ranges( - ranges.len().saturating_sub(normalized.len()) as u64, - ); - // Per-inode drain accounting (one tick per inode whose DATA we - // actually commit, matching the old reporting cardinality — - // time-only commits are not counted as drains). - match reason { - AgentFSWriteBatchDrainReason::Timer => { - crate::profiling::record_agentfs_batcher_drain_timer(); - } - AgentFSWriteBatchDrainReason::Bytes => { - crate::profiling::record_agentfs_batcher_drain_bytes(); - } - AgentFSWriteBatchDrainReason::Explicit => { - crate::profiling::record_agentfs_batcher_drain_explicit(); - } - } - } - to_commit.push((*ino, ranges.len(), normalized)); - } - - if to_commit.is_empty() { - self.cleanup_empty_pending(); - return Ok(more_pending); - } - - let started = Instant::now(); - let conn = self.pool.get_connection().await?; - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - - // Read times_explicit and the stashed explicit times only AFTER the - // IMMEDIATE transaction holds the SQLite write lock (see - // `commit_inode_ranges` for the interleaving argument): explicit - // chmod/chown/utimens that already landed marked the flag / stash - // before their effect, later ones are blocked behind us (or stay - // stashed for the next drain). - let (preserve_times, pending_times): (HashMap, HashMap) = { - let state = self.state.read(); - let preserve = to_commit - .iter() - .map(|(ino, _, _)| { - ( - *ino, - state - .pending - .get(ino) - .map(|batch| batch.times_explicit) - .unwrap_or(false), - ) - }) - .collect(); - let times = to_commit - .iter() - .filter_map(|(ino, _, _)| { - state - .pending - .get(ino) - .and_then(|batch| batch.pending_times) - .map(|t| (*ino, t)) - }) - .collect(); - (preserve, times) - }; - - // Stashed times we actually applied inside this transaction; cleared - // from the pending entries only after the commit succeeds. - let mut applied_times: Vec<(i64, PendingTimeChange)> = Vec::new(); - - for (ino, _count, normalized) in &to_commit { - let mut inode_missing = false; - if !normalized.is_empty() { - let normalized_refs: Vec<_> = normalized - .iter() - .map(|range| WriteRangeRef { - offset: range.offset, - data: range.data.as_slice(), - }) - .collect(); - let file = AgentFSFile { - pool: self.pool.clone(), - ino: *ino, - chunk_size: self.chunk_size, - inline_threshold: self.inline_threshold, - attr_cache: self.attr_cache.clone(), - write_batcher: None, - overlay_reads: true, - _open_guard: None, - }; - match file - .pwrite_ranges_inode_with_conn( - &conn, - &normalized_refs, - preserve_times.get(ino).copied().unwrap_or(false), - pending_times.get(ino), - ) - .await - { - Ok(()) => {} - // The file was unlinked / renamed-over while its writes were - // still pending (git lock and temp files routinely live and - // die within the batch window). Its data is moot: skip it and - // let the post-commit cleanup drop the orphaned ranges - // instead of aborting the whole multi-inode batch. - Err(Error::Fs(FsError::NotFound)) => { - tracing::debug!( - "AgentFS write batcher: dropping pending writes for deleted inode {}", - ino - ); - inode_missing = true; - } - Err(error) => { - let _ = txn.rollback().await; - // Overlay was never modified; ranges remain pending and are - // retried on the next drain. No restore needed. - return Err(error); - } - } - } - - // Stashed explicit times ride the data UPDATE above - // (`write_commit_time_sets`); a time-only entry has no data UPDATE - // to fold into, so it pays one standalone UPDATE inside this same - // transaction. - if !inode_missing { - if let Some(times) = pending_times.get(ino) { - if normalized.is_empty() { - if let Err(error) = apply_pending_times_with_conn(&conn, *ino, times).await - { - let _ = txn.rollback().await; - return Err(error); - } - } - applied_times.push((*ino, *times)); - } - } - } - - txn.commit().await?; - - // Durable now: drop exactly the committed ranges and applied times - // from the overlay, preserving anything enqueued during the commit. - for (ino, times) in &applied_times { - self.clear_applied_times(*ino, times); - } - let committed_counts: Vec<(i64, usize)> = to_commit - .iter() - .map(|(ino, count, _)| (*ino, *count)) - .collect(); - self.remove_committed_prefix(&committed_counts); - for (ino, _, _) in &to_commit { - self.attr_cache.remove(*ino); - } - self.cleanup_empty_pending(); - // Anything still pending (ranges enqueued during the commit, inodes - // beyond the per-txn bound, stashed times that arrived mid-commit) is - // never stranded: the coalescing scheduler picks it up on its next - // pass. Arm it if it is not already running (e.g. explicit drains - // triggered by fsync / kill-switch paths outside the scheduler). - self.ensure_drain_scheduled(); - crate::profiling::record_agentfs_batcher_commit_latency(started.elapsed()); - crate::profiling::record_agentfs_batcher_commit_txn(to_commit.len() as u64); - - Ok(more_pending) - } - - /// Arm the single coalescing drain scheduler if it is not already armed - /// and there is pending work left. Cheap safety net used after drains so - /// residual pending state (ranges enqueued mid-commit, stashed times, - /// inodes beyond a bounded transaction) always has a scheduled commit. - fn ensure_drain_scheduled(self: &Arc) { - let arm = { - let mut state = self.state.write(); - if !state.drain_scheduled && !state.pending.is_empty() { - state.drain_scheduled = true; - true - } else { - false - } - }; - if arm { - self.spawn_drain_scheduler(); - } - } - - /// Spawn the coalescing drain scheduler task. Exactly one instance runs - /// while `state.drain_scheduled` is true; it exits (and clears the flag) - /// only once nothing is pending. - fn spawn_drain_scheduler(self: &Arc) { - let batcher = Arc::clone(self); - tokio::spawn(async move { - batcher.run_drain_scheduler().await; - }); - } - - /// Single coalescing drain scheduler (cross-inode group commit). - /// - /// Instead of one timer task per written inode — which degenerated into a - /// storm of small, serialized SQLite transactions during a git-clone burst - /// — ONE task is armed when the first pending write arrives. Each cycle it - /// sleeps `AGENTFS_BATCH_MS` so concurrent writers coalesce, then commits - /// everything that is pending at that instant in as few `BEGIN IMMEDIATE` - /// transactions as the per-transaction bounds allow - /// (`AGENTFS_BATCH_TXN_INODES` / `AGENTFS_BATCH_TXN_BYTES`), back-to-back. - /// Writes that arrive while a commit is in flight are picked up by the - /// next cycle. The task exits only when nothing is pending; an enqueue - /// that observes `drain_scheduled == false` arms a fresh one. Explicit - /// drains (fsync, finalize, kill-switch paths) and the Bytes triggers are - /// unaffected — they keep draining synchronously on their own call sites. - async fn run_drain_scheduler(self: Arc) { - loop { - tokio::time::sleep(self.batch_ms).await; - - // One pass: commit everything pending at this instant, splitting - // into bounded back-to-back transactions when over the per-txn - // bounds. On error the overlay is left intact (commit-then-remove) - // and the pass is retried on the next cycle. - loop { - match self - .drain_pending_batched(AgentFSWriteBatchDrainReason::Timer, None) - .await - { - Ok(true) => continue, - Ok(false) => break, - Err(error) => { - tracing::warn!( - "AgentFS write batcher: scheduled group drain failed (will retry): {}", - error - ); - break; - } - } - } - - // Exit only when nothing is pending. The flag is cleared under the - // same write lock that observes the empty map, so a concurrent - // enqueue either sees the flag still set (this loop continues and - // commits it) or sees it cleared and arms a fresh scheduler. - let exit = { - let mut state = self.state.write(); - if state.pending.is_empty() { - state.drain_scheduled = false; - true - } else { - false - } - }; - if exit { - return; - } - } - } - - async fn commit_inode_ranges(&self, ino: i64, ranges: &[WriteRange]) -> Result<()> { - let range_refs: Vec<_> = ranges - .iter() - .map(|range| WriteRangeRef { - offset: range.offset, - data: range.data.as_slice(), - }) - .collect(); - let normalized = normalize_write_ranges(&range_refs)?; - if normalized.is_empty() { - return Ok(()); - } - - crate::profiling::record_agentfs_batcher_coalesced_ranges( - ranges.len().saturating_sub(normalized.len()) as u64, - ); - - let started = Instant::now(); - let conn = self.pool.get_connection().await?; - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - // Read the times_explicit flag and the stashed explicit times only - // AFTER the IMMEDIATE transaction holds the SQLite write lock: any - // chmod/chown/utimens that already applied its effect marked the flag - // / stash before that, and any later one is now blocked behind this - // transaction (or stays stashed for the next drain). Reading here - // therefore gives the correct preserve-vs-stamp decision for every - // interleaving (see `mark_times_explicit` / `stash_pending_times`). - let (preserve_times, pending_times) = { - let state = self.state.read(); - let entry = state.pending.get(&ino); - ( - entry.map(|batch| batch.times_explicit).unwrap_or(false), - entry.and_then(|batch| batch.pending_times), - ) - }; - let file = AgentFSFile { - pool: self.pool.clone(), - ino, - chunk_size: self.chunk_size, - inline_threshold: self.inline_threshold, - attr_cache: self.attr_cache.clone(), - write_batcher: None, - overlay_reads: true, - _open_guard: None, - }; - let normalized_refs: Vec<_> = normalized - .iter() - .map(|range| WriteRangeRef { - offset: range.offset, - data: range.data.as_slice(), - }) - .collect(); - let mut result = file - .pwrite_ranges_inode_with_conn( - &conn, - &normalized_refs, - preserve_times, - pending_times.as_ref(), - ) - .await; - // Stashed explicit times ride the data UPDATE (`write_commit_time_sets`); - // only a time-only commit (no data ranges) needs a standalone UPDATE in - // this same transaction. - if result.is_ok() && normalized_refs.is_empty() { - if let Some(times) = &pending_times { - result = apply_pending_times_with_conn(&conn, ino, times).await; - } - } - - match result { - Ok(()) => { - txn.commit().await?; - if let Some(times) = &pending_times { - self.clear_applied_times(ino, times); - } - self.attr_cache.remove(ino); - crate::profiling::record_agentfs_batcher_commit_latency(started.elapsed()); - crate::profiling::record_agentfs_batcher_commit_txn(1); - Ok(()) - } - // Deleted while pending (see drain_pending_batched): treat as - // committed so the caller drops the moot ranges from the overlay. - Err(Error::Fs(FsError::NotFound)) => { - let _ = txn.rollback().await; - tracing::debug!( - "AgentFS write batcher: dropping pending writes for deleted inode {}", - ino - ); - Ok(()) - } - Err(error) => { - let _ = txn.rollback().await; - Err(error) - } - } - } - - /// Tier 4 corruption fix: after a drain has durably committed a snapshot of - /// pending ranges to SQLite, drop exactly those ranges from the overlay. - /// Snapshots are taken from the front of each inode's append-only `ranges` - /// vec, and enqueues only ever append, so the committed ranges are the first - /// `count` entries; ranges appended during the commit are preserved. The - /// `.min(len)` guard tolerates a concurrent `truncate_pending`/`discard_pending` - /// having shrunk or removed the entry. Entries that still have ranges or - /// stashed explicit times are kept; the coalescing scheduler (or the - /// caller's `ensure_drain_scheduled`) commits them on a later pass. - fn remove_committed_prefix(&self, committed: &[(i64, usize)]) { - let mut state = self.state.write(); - for &(ino, count) in committed { - let (removed_bytes, empty) = { - let Some(entry) = state.pending.get_mut(&ino) else { - continue; - }; - let n = count.min(entry.ranges.len()); - let removed_bytes: usize = entry.ranges.drain(..n).map(|r| r.data.len()).sum(); - entry.pending_bytes = entry.pending_bytes.saturating_sub(removed_bytes); - // An entry with stashed explicit times but no ranges is NOT - // drained yet — keep it so a later drain commits the times. - (removed_bytes, entry.is_drained()) - }; - state.total_pending_bytes = state.total_pending_bytes.saturating_sub(removed_bytes); - if empty { - state.pending.remove(&ino); - } - } - state.debug_assert_total(); - } - - /// Remove pending entries that have nothing left to commit (no ranges and - /// no stashed explicit times). Clears their attr cache entry too. - fn cleanup_empty_pending(&self) { - let removed: Vec = { - let mut state = self.state.write(); - let empties: Vec = state - .pending - .iter() - .filter(|(_, b)| b.is_drained()) - .map(|(ino, _)| *ino) - .collect(); - for ino in &empties { - if let Some(b) = state.pending.remove(ino) { - state.total_pending_bytes = - state.total_pending_bytes.saturating_sub(b.pending_bytes); - } - } - state.debug_assert_total(); - empties - }; - for ino in removed { - self.attr_cache.remove(ino); - } - } - - // ----- Tier Four: in-memory overlay read API ----- - // - // These methods let `AgentFSFile::pread` / `getattr` / `truncate` consult - // the batcher's pending state directly, instead of forcing a synchronous - // SQLite drain for read-after-write consistency. The drain becomes a - // pure durability operation, only triggered by explicit `fsync` / - // destroy / timer / bytes triggers. - - /// Snapshot pending writes for `ino` overlapping `[offset, offset+size)`. - /// Returned ranges are normalised (non-overlapping, sorted) and clipped - /// to the requested window. The batcher's pending state is not modified. - /// Callers merge the result over SQLite data with "pending wins" - /// semantics; see `AgentFSFile::pread`. - fn peek_pending(&self, ino: i64, offset: u64, size: u64) -> Vec { - if size == 0 { - return Vec::new(); - } - let read_end = match offset.checked_add(size) { - Some(end) => end, - None => return Vec::new(), - }; - // Read-lock: many concurrent readers OK; writers block briefly during - // enqueue. Crucially, no `.await` is performed while the guard is - // held, so a sync `parking_lot::RwLock` is safe inside an async fn. - let state = self.state.read(); - let Some(batch) = state.pending.get(&ino) else { - return Vec::new(); - }; - if batch.ranges.is_empty() { - return Vec::new(); - } - let refs: Vec<_> = batch - .ranges - .iter() - .map(|r| WriteRangeRef { - offset: r.offset, - data: r.data.as_slice(), - }) - .collect(); - let normalized = match normalize_write_ranges(&refs) { - Ok(n) => n, - Err(_) => return Vec::new(), - }; - normalized - .into_iter() - .filter_map(|range| { - let r_end = range.offset + range.data.len() as u64; - if r_end <= offset || range.offset >= read_end { - return None; - } - let clip_start = offset.max(range.offset); - let clip_end = read_end.min(r_end); - if clip_end <= clip_start { - return None; - } - let skip = (clip_start - range.offset) as usize; - let take = (clip_end - clip_start) as usize; - Some(NormalizedWriteRange { - offset: clip_start, - data: range.data[skip..skip + take].to_vec(), - }) - }) - .collect() - } - - /// Fast path for "does this inode have ANY pending write?" — used by - /// readers to skip the heavier `peek_pending_max_end` / `peek_pending` - /// calls entirely when the batcher has nothing for the inode. Read lock, - /// O(1) HashMap hit. - fn has_pending(&self, ino: i64) -> bool { - let state = self.state.read(); - state - .pending - .get(&ino) - .map(|b| !b.ranges.is_empty()) - .unwrap_or(false) - } - - /// Largest write end (offset + length) for `ino` across all pending - /// ranges. Returns `None` if no pending writes for this inode. Callers - /// OR this with the SQLite-stored `fs_inode.size` to compute the - /// file-size view exposed to readers (so a write that grows the file is - /// visible to subsequent `getattr` even before the timer drain commits - /// it to SQLite). - fn peek_pending_max_end(&self, ino: i64) -> Option { - let state = self.state.read(); - let batch = state.pending.get(&ino)?; - batch - .ranges - .iter() - .map(|r| r.offset.saturating_add(r.data.len() as u64)) - .max() - } - - /// Record that an explicit attribute change (chmod / chown / utimens) was - /// applied to fs_inode for `ino` after the writes currently pending for - /// it. The eventual data commit must then preserve mtime/ctime instead of - /// stamping the commit time (see `commit_inode_ranges`). No-op when the - /// inode has nothing pending — there is no deferred commit to clobber the - /// attributes in that case. - fn mark_times_explicit(&self, ino: i64) { - let mut state = self.state.write(); - if let Some(batch) = state.pending.get_mut(&ino) { - batch.times_explicit = true; - } - } - - /// Stash explicitly-set `utimens` values in the inode's pending entry so - /// the next batched drain commits them inside the SAME transaction as any - /// pending data (one extra UPDATE statement, zero dedicated foreground - /// transactions). The entry is created when the inode has nothing pending - /// yet: the kernel's writeback SETATTR routinely lands after the inode's - /// data already drained (or before its flush arrives), and creating the - /// entry both removes the per-file fallback transaction and guarantees a - /// scheduled drain applies the times. `merge_pending_view` keeps the - /// change visible to getattr/lookup immediately. - fn stash_pending_times(self: &Arc, ino: i64, change: PendingTimeChange) { - if change.is_empty() { - return; - } - { - let mut state = self.state.write(); - state - .pending - .entry(ino) - .or_insert_with(PendingInodeWrites::new) - .pending_times - .get_or_insert_with(PendingTimeChange::default) - .apply(&change); - } - // A times-only entry still needs a scheduled drain to commit it. - self.ensure_drain_scheduled(); - } - - /// Snapshot the stashed explicit times for `ino` (if any) without removing - /// them. Drains read this AFTER their `BEGIN IMMEDIATE` holds the write - /// lock and clear exactly what they applied once the commit succeeds - /// (commit-then-remove, mirroring the data-range discipline). Readers use - /// it to overlay not-yet-committed times onto fs_inode rows. - fn peek_pending_times(&self, ino: i64) -> Option { - let state = self.state.read(); - state.pending.get(&ino).and_then(|b| b.pending_times) - } - - /// After a successful commit, drop the stashed time fields that were - /// actually applied. Field-wise equality keeps any NEWER stash that - /// arrived while the transaction was in flight (it will be committed by - /// the next drain instead of being silently lost). - fn clear_applied_times(&self, ino: i64, applied: &PendingTimeChange) { - let mut state = self.state.write(); - let Some(batch) = state.pending.get_mut(&ino) else { - return; - }; - let Some(times) = &mut batch.pending_times else { - return; - }; - if times.atime == applied.atime { - times.atime = None; - } - if times.mtime == applied.mtime { - times.mtime = None; - } - if times.ctime == applied.ctime { - times.ctime = None; - } - if times.is_empty() { - batch.pending_times = None; - } - } - - /// Drop any pending bytes beyond `new_size` and shrink ranges that span - /// the truncation boundary. Called by `AgentFSFile::truncate` so the - /// overlay agrees with the post-truncate file state without needing to - /// drain first. - fn truncate_pending(&self, ino: i64, new_size: u64) { - let mut state = self.state.write(); - let (old_bytes, new_bytes, now_empty) = { - let Some(batch) = state.pending.get_mut(&ino) else { - return; - }; - let old_bytes = batch.pending_bytes; - let mut new_bytes = 0usize; - batch.ranges.retain_mut(|range| { - let r_end = range.offset.saturating_add(range.data.len() as u64); - if range.offset >= new_size { - return false; - } - if r_end > new_size { - let keep = (new_size - range.offset) as usize; - range.data.truncate(keep); - } - new_bytes = new_bytes.saturating_add(range.data.len()); - !range.data.is_empty() - }); - batch.pending_bytes = new_bytes; - (old_bytes, new_bytes, batch.ranges.is_empty()) - }; - state.total_pending_bytes = state - .total_pending_bytes - .saturating_sub(old_bytes) - .saturating_add(new_bytes); - if now_empty { - state.pending.remove(&ino); - } - state.debug_assert_total(); - } - - /// Discard every pending write for `ino`. Used by `AgentFS::unlink` - /// after the inode row is deleted, to avoid `fs_data` orphan rows when - /// the timer later tries to commit ranges for a no-longer-existent ino. - fn discard_pending(&self, ino: i64) { - let mut state = self.state.write(); - if let Some(batch) = state.pending.remove(&ino) { - state.total_pending_bytes = state - .total_pending_bytes - .saturating_sub(batch.pending_bytes); - } - state.debug_assert_total(); - } -} - -/// One node for [`AgentFS::import_entries`]. `path` is relative to the import -/// root and '/'-separated; parent directories must precede their children. -#[derive(Debug, Clone)] -pub struct ImportEntry { - pub path: String, - /// Full `st_mode` bits (S_IFDIR / S_IFREG / S_IFLNK plus permissions). - pub mode: u32, - /// File content, or the symlink target bytes; empty for directories. - pub data: Vec, -} - -/// Result row for one imported node: echoes the exact `ino`/`mode`/`size` -/// the filesystem will serve so callers can fabricate externally-consistent -/// stat metadata (e.g. a git index) without re-reading content. -#[derive(Debug, Clone)] -pub struct ImportedEntry { - pub path: String, - pub ino: i64, - pub mode: u32, - pub size: u64, -} - -/// Ownership and timestamps applied to every node of one bulk import. -#[derive(Debug, Clone)] -pub struct ImportOptions { - pub uid: u32, - pub gid: u32, - /// (secs, nanos) stamped as atime/mtime/ctime on every imported inode. - pub timestamp: (i64, i64), -} - -/// A streaming bulk import started by [`AgentFS::begin_import`]. Holds one -/// pooled connection plus the directory-path -> ino map across -/// [`ImportSession::import_chunk`] calls, so a producer can feed entries as -/// they become available (e.g. as `git cat-file --batch` emits blobs) -/// instead of buffering the whole tree in memory. The ordering contract -/// matches [`AgentFS::import_entries`]: every parent directory must appear -/// in some chunk before (or in the same chunk as) its children. -pub struct ImportSession { - fs: AgentFS, - conn: crate::connection_pool::PooledConnection, - dest_parent: i64, - opts: ImportOptions, - dir_inos: HashMap, - results: Vec, -} - -impl ImportSession { - /// Import one batch of entries. Parent directories imported by earlier - /// chunks (or earlier in this chunk) resolve normally; a parent that has - /// never been imported yields `FsError::NotFound`. - pub async fn import_chunk(&mut self, entries: &[ImportEntry]) -> Result<()> { - self.fs - .import_chunk_with_conn( - &self.conn, - self.dest_parent, - &self.opts, - &mut self.dir_inos, - &mut self.results, - entries, - ) - .await - } - - /// Finish the import and return one [`ImportedEntry`] per imported node, - /// in the order the entries were fed. - pub fn finish(self) -> Vec { - self.fs.invalidate_attr(self.dest_parent); - self.results - } -} - -/// A filesystem backed by SQLite -#[derive(Clone)] -pub struct AgentFS { - pool: ConnectionPool, - db_path: Option>, - chunk_size: usize, - inline_threshold: usize, - /// Cache for directory entry lookups (shared across clones) - dentry_cache: Arc, - /// Cache for negative directory entry lookups (shared across clones) - negative_dentry_cache: Arc, - /// Cache for inode attributes (shared across clones) - attr_cache: Arc, - /// Optional write batcher used by FUSE writeback mode. - write_batcher: Option>, - /// Tier 4 escape hatch: when false (`AGENTFS_OVERLAY_READS=0`), the SDK - /// behaves like Tier 3 — every pwrite drains, every pread drains, - /// `merge_pending_view` is a no-op. ON by default. - overlay_reads: bool, - /// Live open-handle registry for deferred orphan reaping (see - /// [`OpenInodes`]). - open_inodes: Arc, - /// Emits a profiling summary when the final filesystem clone is dropped. - _profile_report: Arc, -} - -/// Tracks inodes with live `AgentFSFile` handles so unlink and -/// rename-replace can defer row deletion: POSIX requires an -/// unlinked-but-open file to stay readable and writable until its last -/// handle closes. `nlink = 0` in `fs_inode` is the crash-safe orphan -/// marker — deferred inodes are queued here when their last handle drops -/// and reaped by `process_deferred_reaps` (unlink/rename/finalize) or, after -/// a crash, by the mount-time sweep. -#[derive(Default)] -pub(crate) struct OpenInodes { - inner: Mutex, -} - -#[derive(Default)] -struct OpenInodesInner { - counts: HashMap, - orphaned: HashSet, - reap_queue: Vec, -} - -impl OpenInodes { - fn guard(self: &Arc, ino: i64) -> OpenInodeGuard { - let mut inner = self.inner.lock().unwrap(); - *inner.counts.entry(ino).or_insert(0) += 1; - OpenInodeGuard { - registry: Arc::clone(self), - ino, - } - } - - /// Marks the inode for deferred reaping when handles are live. - /// Returns true when the caller must NOT delete the rows yet. - fn defer_reap_if_open(&self, ino: i64) -> bool { - let mut inner = self.inner.lock().unwrap(); - if inner.counts.contains_key(&ino) { - inner.orphaned.insert(ino); - true - } else { - false - } - } - - fn release(&self, ino: i64) { - let mut inner = self.inner.lock().unwrap(); - match inner.counts.get_mut(&ino) { - Some(count) if *count > 1 => *count -= 1, - Some(_) => { - inner.counts.remove(&ino); - if inner.orphaned.remove(&ino) { - inner.reap_queue.push(ino); - } - } - None => {} - } - } - - fn take_reap_queue(&self) -> Vec { - let mut inner = self.inner.lock().unwrap(); - std::mem::take(&mut inner.reap_queue) - } - - fn requeue_reaps(&self, inos: Vec) { - let mut inner = self.inner.lock().unwrap(); - inner.reap_queue.extend(inos); - } - - fn has_pending_reaps(&self) -> bool { - !self.inner.lock().unwrap().reap_queue.is_empty() - } -} - -/// RAII registration of one `AgentFSFile` in [`OpenInodes`]. -pub(crate) struct OpenInodeGuard { - registry: Arc, - ino: i64, -} - -impl Drop for OpenInodeGuard { - fn drop(&mut self) { - self.registry.release(self.ino); - } -} - -/// An open file handle for AgentFS. -/// -/// This struct holds the inode number resolved at open time, allowing -/// efficient read/write/fsync operations without path lookups. -pub struct AgentFSFile { - pool: ConnectionPool, - ino: i64, - chunk_size: usize, - inline_threshold: usize, - attr_cache: Arc, - write_batcher: Option>, - /// Same semantics as the field on `AgentFS`; cloned at open time so the - /// hot read/write path doesn't have to chase an extra indirection. - overlay_reads: bool, - /// None only for the batcher's ephemeral internal handles; user-visible - /// handles register so unlink defers inode reaping while they live. - _open_guard: Option, -} - -struct FileStorage { - size: u64, - storage_kind: i64, - inline_data: Option>, -} - -struct WriteRangeRef<'a> { - offset: u64, - data: &'a [u8], -} - -#[derive(Clone)] -struct NormalizedWriteRange { - offset: u64, - data: Vec, -} - -impl NormalizedWriteRange { - fn end(&self) -> u64 { - self.offset + self.data.len() as u64 - } -} - -fn current_timestamp() -> Result<(i64, i64)> { - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - Ok((dur.as_secs() as i64, dur.subsec_nanos() as i64)) -} - -fn normalize_write_ranges(ranges: &[WriteRangeRef<'_>]) -> Result> { - let mut merged_ranges: BTreeMap> = BTreeMap::new(); - - for range in ranges { - if range.data.is_empty() { - continue; - } - - let data_len = u64::try_from(range.data.len()) - .map_err(|_| Error::Internal("file write length overflow".to_string()))?; - let write_start = range.offset; - let write_end = write_start - .checked_add(data_len) - .ok_or_else(|| Error::Internal("file write offset overflow".to_string()))?; - let mut start = write_start; - let mut end = write_end; - let mut existing_ranges = Vec::new(); - - if let Some((&prev_start, prev_data)) = merged_ranges.range(..=write_start).next_back() { - let prev_end = prev_start - .checked_add(prev_data.len() as u64) - .ok_or_else(|| Error::Internal("file write offset overflow".to_string()))?; - - if prev_end >= write_start { - let prev_data = prev_data.clone(); - merged_ranges.remove(&prev_start); - - start = prev_start; - end = end.max(prev_end); - existing_ranges.push((prev_start, prev_data)); - } - } - - loop { - let next = merged_ranges - .range(start..) - .next() - .map(|(&next_start, next_data)| (next_start, next_data.clone())); - - let Some((next_start, next_data)) = next else { - break; - }; - - if next_start > end { - break; - } - - let next_end = next_start - .checked_add(next_data.len() as u64) - .ok_or_else(|| Error::Internal("file write offset overflow".to_string()))?; - merged_ranges.remove(&next_start); - - end = end.max(next_end); - existing_ranges.push((next_start, next_data)); - } - - let merged_len = usize::try_from(end - start) - .map_err(|_| Error::Internal("file write range too large".to_string()))?; - let mut merged = vec![0; merged_len]; - for (range_start, range_data) in existing_ranges { - let range_offset = usize::try_from(range_start - start) - .map_err(|_| Error::Internal("file write range too large".to_string()))?; - merged[range_offset..range_offset + range_data.len()].copy_from_slice(&range_data); - } - - let write_offset = usize::try_from(write_start - start) - .map_err(|_| Error::Internal("file write range too large".to_string()))?; - merged[write_offset..write_offset + range.data.len()].copy_from_slice(range.data); - - merged_ranges.insert(start, merged); - } - - Ok(merged_ranges - .into_iter() - .map(|(offset, data)| NormalizedWriteRange { offset, data }) - .collect()) -} - -fn dense_after_inline_write_batch( - current_size: u64, - new_size: u64, - ranges: &[NormalizedWriteRange], -) -> bool { - let mut covered_end = current_size; - - for range in ranges { - let range_end = range.end(); - if range_end <= covered_end { - continue; - } - if range.offset > covered_end { - return false; - } - covered_end = range_end; - if covered_end >= new_size { - return true; - } - } - - covered_end >= new_size -} - -#[async_trait] -impl File for AgentFSFile { - async fn pread(&self, offset: u64, size: u64) -> Result> { - // Tier Four: NO `drain_writes()` prelude. Read SQLite-resident bytes - // (committed state) and overlay pending writes from the in-memory - // batcher snapshot. Together they form a read-after-write consistent - // view without forcing a SQLite commit on the read path. - // - // Ordering matters: peek the batcher state BEFORE acquiring a pool - // connection, and release the connection BEFORE the splice loop. Long - // pread workloads (parallel git-grep) saturate the 8-slot pool, and - // holding a connection across `state.lock().await` starves the timer - // drain task that also needs a connection to commit. - if size == 0 { - return Ok(Vec::new()); - } - // Escape hatch: when overlay reads are disabled, behave like Tier 3 - // — drain the inode's pending writes before reading SQLite. Same - // wire result, slower but battle-tested. - if !self.overlay_reads { - self.drain_writes().await?; - } - let pending_max_end = match &self.write_batcher { - Some(batcher) if self.overlay_reads && batcher.has_pending(self.ino) => { - batcher.peek_pending_max_end(self.ino) - } - _ => None, - }; - let pending_ranges = match &self.write_batcher { - Some(batcher) if pending_max_end.is_some() => { - batcher.peek_pending(self.ino, offset, size) - } - _ => Vec::new(), - }; - - let conn = self.pool.get_connection().await?; - let metadata = self.file_storage_with_conn(&conn).await?; - let effective_size = match pending_max_end { - Some(end) => metadata.size.max(end), - None => metadata.size, - }; - - if offset >= effective_size { - return Ok(Vec::new()); - } - let read_size = size.min(effective_size - offset); - - let base_window = if offset < metadata.size { - (metadata.size - offset).min(read_size) - } else { - 0 - }; - let mut result = if base_window > 0 { - let mut buf = self - .read_inode_with_conn(&conn, offset, base_window) - .await?; - buf.resize(read_size as usize, 0); - buf - } else { - vec![0u8; read_size as usize] - }; - drop(conn); - - for range in pending_ranges { - if range.offset >= offset + read_size { - continue; - } - let dst_off = (range.offset - offset) as usize; - if dst_off >= result.len() { - continue; - } - let end = (dst_off + range.data.len()).min(result.len()); - result[dst_off..end].copy_from_slice(&range.data[..end - dst_off]); - } - - Ok(result) - } - - async fn pwrite(&self, offset: u64, data: &[u8]) -> Result<()> { - if data.is_empty() { - return Ok(()); - } - // Tier Four: with the batcher wired AND overlay reads enabled, - // route through enqueue so the overlay holds the write and readers - // see it via `pread`'s peek_pending merge. Drain only on - // fsync/destroy/timer. When `AGENTFS_OVERLAY_READS=0` the - // overlay-reads escape hatch is engaged: skip the batcher and commit - // directly so the legacy Tier 3 read path (which drains before - // reading) sees the write. - if let Some(batcher) = &self.write_batcher { - if self.overlay_reads { - return batcher - .enqueue( - self.ino, - vec![WriteRange { - offset, - data: data.to_vec(), - }], - ) - .await; - } - } - // Fallback (no batcher): direct commit. drain_writes is a no-op - // when there's no batcher, but keeping the call here makes the - // contract explicit. - self.drain_writes().await?; - let conn = self.pool.get_connection().await?; - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let ranges = [WriteRangeRef { offset, data }]; - let result = self - .pwrite_ranges_inode_with_conn(&conn, &ranges, false, None) - .await; - match result { - Ok(()) => { - txn.commit().await?; - self.attr_cache.remove(self.ino); - Ok(()) - } - Err(e) => { - let _ = txn.rollback().await; - Err(e) - } - } - } - - async fn pwrite_ranges(&self, ranges: Vec) -> Result<()> { - if ranges.iter().all(|range| range.data.is_empty()) { - return Ok(()); - } - // Tier Four: route through the batcher when overlay reads are - // enabled; otherwise commit immediately (escape hatch — see pwrite). - if let Some(batcher) = &self.write_batcher { - if self.overlay_reads { - return batcher.enqueue(self.ino, ranges).await; - } - } - self.drain_writes().await?; - - let conn = self.pool.get_connection().await?; - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let range_refs: Vec<_> = ranges - .iter() - .map(|range| WriteRangeRef { - offset: range.offset, - data: range.data.as_slice(), - }) - .collect(); - let result = self - .pwrite_ranges_inode_with_conn(&conn, &range_refs, false, None) - .await; - match result { - Ok(()) => { - txn.commit().await?; - self.attr_cache.remove(self.ino); - Ok(()) - } - Err(e) => { - let _ = txn.rollback().await; - Err(e) - } - } - } - - async fn pwrite_ranges_batched(&self, ranges: Vec) -> Result<()> { - if ranges.iter().all(|range| range.data.is_empty()) { - return Ok(()); - } - - if let Some(batcher) = &self.write_batcher { - batcher.enqueue(self.ino, ranges).await - } else { - self.pwrite_ranges(ranges).await - } - } - - async fn truncate(&self, new_size: u64) -> Result<()> { - // Tier Four: shrink the in-memory overlay BEFORE touching SQLite, so - // a concurrent reader doesn't observe pending bytes past the new EOF - // between the SQLite truncate and the batcher catching up. - if let Some(batcher) = &self.write_batcher { - batcher.truncate_pending(self.ino, new_size); - } - // Drain remaining pending so the SQLite truncate sees a consistent - // size. With truncate_pending called above, the only pending left is - // for offsets < new_size, which will be applied by the timer / next - // drain trigger. We still drain here so the SQLite size after this - // call exactly matches `new_size`. - self.drain_writes().await?; - let conn = self.pool.get_connection().await?; - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let result = self.truncate_inode_with_conn(&conn, new_size).await; - match result { - Ok(()) => { - txn.commit().await?; - self.attr_cache.remove(self.ino); - Ok(()) - } - Err(e) => { - let _ = txn.rollback().await; - Err(e) - } - } - } - - async fn fsync(&self) -> Result<()> { - // Tier Four: fsync remains the explicit durability barrier — drain the - // batcher so the WAL checkpoint that follows captures every pending - // write. - self.drain_writes().await?; - let conn = self.pool.get_connection().await?; - conn.prepare_cached(DURABLE_SYNCHRONOUS_SQL) - .await? - .execute(()) - .await?; - checkpoint_wal(&conn).await?; - conn.prepare_cached(BASELINE_SYNCHRONOUS_SQL) - .await? - .execute(()) - .await?; - Ok(()) - } - - async fn fstat(&self) -> Result { - self.drain_writes().await?; - if let Some(stats) = self.attr_cache.get(self.ino) { - return Ok(stats); - } - - let conn = self.pool.get_connection().await?; - let mut stmt = conn - .prepare_cached("SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime, rdev, atime_nsec, mtime_nsec, ctime_nsec FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((self.ino,)).await?; - - if let Some(row) = rows.next().await? { - let stats = AgentFS::build_stats_from_row(&row)?; - self.attr_cache.insert(stats.clone()); - Ok(stats) - } else { - Err(FsError::NotFound.into()) - } - } - - async fn drain_writes(&self) -> Result<()> { - if let Some(batcher) = &self.write_batcher { - batcher - .drain_inode(self.ino, AgentFSWriteBatchDrainReason::Explicit) - .await?; - } - Ok(()) - } -} - -impl AgentFSFile { - async fn read_inode_with_conn( - &self, - conn: &Connection, - offset: u64, - size: u64, - ) -> Result> { - let metadata = self.file_storage_with_conn(conn).await?; - - if offset >= metadata.size || size == 0 { - return Ok(Vec::new()); - } - - let size = std::cmp::min(size, metadata.size - offset); - if metadata.storage_kind == STORAGE_INLINE { - let mut result = Vec::with_capacity(size as usize); - let inline_data = metadata.inline_data.unwrap_or_default(); - let start = offset as usize; - let requested = size as usize; - - if start < inline_data.len() { - let available = std::cmp::min(inline_data.len() - start, requested); - result.extend_from_slice(&inline_data[start..start + available]); - } - - if result.len() < requested { - result.resize(requested, 0); - } - - return Ok(result); - } - - self.read_chunked_inode_with_conn(conn, offset, size).await - } - - async fn read_chunked_inode_with_conn( - &self, - conn: &Connection, - offset: u64, - size: u64, - ) -> Result> { - let chunk_size = self.chunk_size as u64; - let start_chunk = offset / chunk_size; - let end_chunk = (offset + size).saturating_sub(1) / chunk_size; - - let mut stmt = conn - .prepare_cached("SELECT chunk_index, data FROM fs_data WHERE ino = ? AND chunk_index >= ? AND chunk_index <= ? ORDER BY chunk_index") - .await?; - crate::profiling::record_chunk_read_query(); - let mut rows = stmt - .query((self.ino, start_chunk as i64, end_chunk as i64)) - .await?; - - let mut result = Vec::with_capacity(size as usize); - let start_offset_in_chunk = (offset % chunk_size) as usize; - let mut next_expected_chunk = start_chunk; - let mut chunks_read = 0u64; - - while let Some(row) = rows.next().await? { - chunks_read += 1; - let chunk_index = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u64; - - while next_expected_chunk < chunk_index && result.len() < size as usize { - let skip = if next_expected_chunk == start_chunk { - start_offset_in_chunk - } else { - 0 - }; - let zeros_needed = - std::cmp::min(chunk_size as usize - skip, size as usize - result.len()); - result.extend(std::iter::repeat_n(0u8, zeros_needed)); - next_expected_chunk += 1; - } - - if let Ok(Value::Blob(chunk_data)) = row.get_value(1) { - let skip = if chunk_index == start_chunk { - start_offset_in_chunk - } else { - 0 - }; - if skip >= chunk_data.len() { - let zeros_needed = - std::cmp::min(chunk_size as usize - skip, size as usize - result.len()); - result.extend(std::iter::repeat_n(0u8, zeros_needed)); - } else { - let remaining = size as usize - result.len(); - let take = std::cmp::min(chunk_data.len() - skip, remaining); - result.extend_from_slice(&chunk_data[skip..skip + take]); - - let chunk_end = skip + take; - if chunk_end < chunk_size as usize && result.len() < size as usize { - let zeros_needed = std::cmp::min( - chunk_size as usize - chunk_end, - size as usize - result.len(), - ); - result.extend(std::iter::repeat_n(0u8, zeros_needed)); - } - } - } - next_expected_chunk = chunk_index + 1; - } - - if result.len() < size as usize { - result.resize(size as usize, 0); - } - - crate::profiling::record_chunk_read_chunks(chunks_read); - Ok(result) - } - - /// `preserve_times`: when true (deferred batcher commits racing an explicit - /// chmod/chown/utimens), leave mtime/ctime untouched instead of stamping - /// the commit time — the explicitly-set attributes logically happened - /// after these writes and must win. `explicit_times`: stashed setattr - /// values folded into the inode UPDATE itself (see - /// `write_commit_time_sets`). - async fn pwrite_ranges_inode_with_conn( - &self, - conn: &Connection, - ranges: &[WriteRangeRef<'_>], - preserve_times: bool, - explicit_times: Option<&PendingTimeChange>, - ) -> Result<()> { - let ranges = normalize_write_ranges(ranges)?; - if ranges.is_empty() { - return Ok(()); - } - - let metadata = self.file_storage_with_conn(conn).await?; - let write_end = ranges - .iter() - .map(NormalizedWriteRange::end) - .max() - .unwrap_or(metadata.size); - let new_size = std::cmp::max(metadata.size, write_end); - - if metadata.storage_kind == STORAGE_INLINE - && new_size <= self.inline_threshold as u64 - && dense_after_inline_write_batch(metadata.size, new_size, &ranges) - { - let mut inline_data = metadata.inline_data.unwrap_or_default(); - inline_data.resize(metadata.size as usize, 0); - inline_data.resize(new_size as usize, 0); - for range in &ranges { - let start = range.offset as usize; - inline_data[start..start + range.data.len()].copy_from_slice(&range.data); - } - - conn.execute("DELETE FROM fs_data WHERE ino = ?", (self.ino,)) - .await?; - let mut sets = vec!["size = ?", "data_inline = ?", "storage_kind = ?"]; - let mut values: Vec = vec![ - Value::Integer(new_size as i64), - Value::Blob(inline_data), - Value::Integer(STORAGE_INLINE), - ]; - let (time_sets, time_values) = write_commit_time_sets(preserve_times, explicit_times)?; - sets.extend(time_sets); - values.extend(time_values); - values.push(Value::Integer(self.ino)); - let sql = format!("UPDATE fs_inode SET {} WHERE ino = ?", sets.join(", ")); - conn.execute(&sql, values).await?; - return Ok(()); - } - - let mut chunked_ranges = Vec::new(); - if metadata.storage_kind == STORAGE_INLINE { - let mut inline_data = metadata.inline_data.unwrap_or_default(); - inline_data.resize(metadata.size as usize, 0); - conn.execute("DELETE FROM fs_data WHERE ino = ?", (self.ino,)) - .await?; - if !inline_data.is_empty() { - chunked_ranges.push(NormalizedWriteRange { - offset: 0, - data: inline_data, - }); - } - } else { - conn.execute( - "UPDATE fs_inode SET data_inline = NULL, storage_kind = ? WHERE ino = ?", - (STORAGE_CHUNKED, self.ino), - ) - .await?; - } - - chunked_ranges.extend(ranges); - self.write_ranges_chunked_with_conn(conn, &chunked_ranges) - .await?; - - let mut sets = vec!["size = ?", "data_inline = NULL", "storage_kind = ?"]; - let mut values: Vec = vec![ - Value::Integer(new_size as i64), - Value::Integer(STORAGE_CHUNKED), - ]; - let (time_sets, time_values) = write_commit_time_sets(preserve_times, explicit_times)?; - sets.extend(time_sets); - values.extend(time_values); - values.push(Value::Integer(self.ino)); - let sql = format!("UPDATE fs_inode SET {} WHERE ino = ?", sets.join(", ")); - conn.execute(&sql, values).await?; - - Ok(()) - } - - async fn truncate_inode_with_conn(&self, conn: &Connection, new_size: u64) -> Result<()> { - let metadata = self.file_storage_with_conn(conn).await?; - - if metadata.storage_kind == STORAGE_INLINE { - if new_size <= self.inline_threshold as u64 { - let mut inline_data = metadata.inline_data.unwrap_or_default(); - inline_data.resize(metadata.size as usize, 0); - inline_data.resize(new_size as usize, 0); - conn.execute("DELETE FROM fs_data WHERE ino = ?", (self.ino,)) - .await?; - let (now_secs, now_nsec) = current_timestamp()?; - conn.execute( - "UPDATE fs_inode SET size = ?, data_inline = ?, storage_kind = ?, mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?", - ( - new_size as i64, - Value::Blob(inline_data), - STORAGE_INLINE, - now_secs, - now_secs, - now_nsec, - now_nsec, - self.ino, - ), - ) - .await?; - return Ok(()); - } - - let mut inline_data = metadata.inline_data.unwrap_or_default(); - inline_data.resize(metadata.size as usize, 0); - self.transition_inline_to_chunked_with_conn(conn, &inline_data) - .await?; - self.truncate_chunked_data_with_conn(conn, metadata.size, new_size) - .await?; - self.update_chunked_truncate_metadata(conn, new_size) - .await?; - return Ok(()); - } - - if new_size <= self.inline_threshold as u64 { - if let Some(inline_data) = self.read_dense_prefix_for_inline(conn, new_size).await? { - conn.execute("DELETE FROM fs_data WHERE ino = ?", (self.ino,)) - .await?; - let (now_secs, now_nsec) = current_timestamp()?; - conn.execute( - "UPDATE fs_inode SET size = ?, data_inline = ?, storage_kind = ?, mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?", - ( - new_size as i64, - Value::Blob(inline_data), - STORAGE_INLINE, - now_secs, - now_secs, - now_nsec, - now_nsec, - self.ino, - ), - ) - .await?; - return Ok(()); - } - } - - self.truncate_chunked_data_with_conn(conn, metadata.size, new_size) - .await?; - self.update_chunked_truncate_metadata(conn, new_size) - .await?; - Ok(()) - } - - async fn file_storage_with_conn(&self, conn: &Connection) -> Result { - let mut stmt = conn - .prepare_cached("SELECT size, storage_kind, data_inline FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((self.ino,)).await?; - - if let Some(row) = rows.next().await? { - let size = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u64; - let storage_kind = row - .get_value(1) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(STORAGE_CHUNKED); - let inline_data = match row.get_value(2) { - Ok(Value::Blob(data)) => Some(data), - _ => None, - }; - Ok(FileStorage { - size, - storage_kind, - inline_data, - }) - } else { - Err(FsError::NotFound.into()) - } - } - - async fn transition_inline_to_chunked_with_conn( - &self, - conn: &Connection, - inline_data: &[u8], - ) -> Result<()> { - conn.execute("DELETE FROM fs_data WHERE ino = ?", (self.ino,)) - .await?; - - if !inline_data.is_empty() { - self.write_data_at_offset_with_conn(conn, 0, inline_data) - .await?; - } - - conn.execute( - "UPDATE fs_inode SET data_inline = NULL, storage_kind = ? WHERE ino = ?", - (STORAGE_CHUNKED, self.ino), - ) - .await?; - - Ok(()) - } - - async fn read_dense_prefix_for_inline( - &self, - conn: &Connection, - new_size: u64, - ) -> Result>> { - if new_size == 0 { - return Ok(Some(Vec::new())); - } - - let chunk_size = self.chunk_size as u64; - let last_chunk = (new_size - 1) / chunk_size; - let mut inline_data = Vec::with_capacity(new_size as usize); - - let mut stmt = conn - .prepare_cached("SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?") - .await?; - for chunk_idx in 0..=last_chunk { - stmt.reset()?; - let mut rows = stmt.query((self.ino, chunk_idx as i64)).await?; - let Some(row) = rows.next().await? else { - return Ok(None); - }; - let chunk_data = match row.get_value(0) { - Ok(Value::Blob(data)) => data, - _ => return Ok(None), - }; - let remaining = new_size as usize - inline_data.len(); - let needed = std::cmp::min(self.chunk_size, remaining); - if chunk_data.len() < needed { - return Ok(None); - } - inline_data.extend_from_slice(&chunk_data[..needed]); - } - - Ok(Some(inline_data)) - } - - async fn truncate_chunked_data_with_conn( - &self, - conn: &Connection, - current_size: u64, - new_size: u64, - ) -> Result<()> { - let chunk_size = self.chunk_size as u64; - - if new_size == 0 { - conn.execute("DELETE FROM fs_data WHERE ino = ?", (self.ino,)) - .await?; - } else if new_size < current_size { - let last_chunk_idx = (new_size - 1) / chunk_size; - - conn.execute( - "DELETE FROM fs_data WHERE ino = ? AND chunk_index > ?", - (self.ino, last_chunk_idx as i64), - ) - .await?; - - let end_in_last_chunk = ((new_size - 1) % chunk_size + 1) as usize; - if end_in_last_chunk < chunk_size as usize { - let mut stmt = conn - .prepare_cached("SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?") - .await?; - let mut rows = stmt.query((self.ino, last_chunk_idx as i64)).await?; - - if let Some(row) = rows.next().await? { - if let Ok(Value::Blob(chunk_data)) = row.get_value(0) { - if chunk_data.len() > end_in_last_chunk { - conn.execute( - "UPDATE fs_data SET data = ? WHERE ino = ? AND chunk_index = ?", - ( - &chunk_data[..end_in_last_chunk], - self.ino, - last_chunk_idx as i64, - ), - ) - .await?; - } - } - } - } - } else if new_size > current_size { - let last_existing_chunk = if current_size == 0 { - None - } else { - Some((current_size - 1) / chunk_size) - }; - let last_new_chunk = (new_size - 1) / chunk_size; - - if let Some(last_idx) = last_existing_chunk { - let mut stmt = conn - .prepare_cached("SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?") - .await?; - let mut rows = stmt.query((self.ino, last_idx as i64)).await?; - - if let Some(row) = rows.next().await? { - if let Ok(Value::Blob(chunk_data)) = row.get_value(0) { - let current_chunk_len = chunk_data.len(); - let needed_len = if last_idx == last_new_chunk { - ((new_size - 1) % chunk_size + 1) as usize - } else { - chunk_size as usize - }; - - if needed_len > current_chunk_len { - let mut padded = chunk_data.clone(); - padded.resize(needed_len, 0); - conn.execute( - "UPDATE fs_data SET data = ? WHERE ino = ? AND chunk_index = ?", - (&padded[..], self.ino, last_idx as i64), - ) - .await?; - } - } - } - } - - let start_new_chunk = last_existing_chunk.map(|i| i + 1).unwrap_or(0); - for chunk_idx in start_new_chunk..=last_new_chunk { - let chunk_len = if chunk_idx == last_new_chunk { - ((new_size - 1) % chunk_size + 1) as usize - } else { - chunk_size as usize - }; - let zeros = vec![0u8; chunk_len]; - conn.execute( - "INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?)", - (self.ino, chunk_idx as i64, &zeros[..]), - ) - .await?; - } - } - - Ok(()) - } - - async fn update_chunked_truncate_metadata( - &self, - conn: &Connection, - new_size: u64, - ) -> Result<()> { - let (now_secs, now_nsec) = current_timestamp()?; - conn.execute( - "UPDATE fs_inode SET size = ?, data_inline = NULL, storage_kind = ?, mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?", - ( - new_size as i64, - STORAGE_CHUNKED, - now_secs, - now_secs, - now_nsec, - now_nsec, - self.ino, - ), - ) - .await?; - Ok(()) - } - - /// Write data at a specific offset, handling chunk boundaries. - /// Uses a provided connection to allow reuse within a transaction. - async fn write_data_at_offset_with_conn( - &self, - conn: &Connection, - offset: u64, - data: &[u8], - ) -> Result<()> { - let ranges = [WriteRangeRef { offset, data }]; - let ranges = normalize_write_ranges(&ranges)?; - self.write_ranges_chunked_with_conn(conn, &ranges).await - } - - async fn write_ranges_chunked_with_conn( - &self, - conn: &Connection, - ranges: &[NormalizedWriteRange], - ) -> Result<()> { - let chunk_size = self.chunk_size as u64; - - if ranges.is_empty() { - return Ok(()); - } - - let mut select_stmt = conn - .prepare_cached("SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?") - .await?; - let mut insert_stmt = conn - .prepare_cached( - "INSERT OR REPLACE INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?)", - ) - .await?; - - let mut chunks: BTreeMap> = BTreeMap::new(); - - for range in ranges { - let mut written = 0usize; - while written < range.data.len() { - let current_offset = range.offset + written as u64; - let chunk_index = (current_offset / chunk_size) as i64; - let offset_in_chunk = (current_offset % chunk_size) as usize; - - let remaining_in_chunk = self.chunk_size - offset_in_chunk; - let remaining_data = range.data.len() - written; - let to_write = std::cmp::min(remaining_in_chunk, remaining_data); - let write_slice = &range.data[written..written + to_write]; - - if offset_in_chunk == 0 && to_write == self.chunk_size { - chunks.insert(chunk_index, write_slice.to_vec()); - written += to_write; - continue; - } - - if let std::collections::btree_map::Entry::Vacant(entry) = chunks.entry(chunk_index) - { - let mut rows = select_stmt.query((self.ino, chunk_index)).await?; - let chunk_data = if let Some(row) = rows.next().await? { - row.get_value(0) - .ok() - .and_then(|v| { - if let Value::Blob(b) = v { - Some(b) - } else { - None - } - }) - .unwrap_or_default() - } else { - Vec::new() - }; - select_stmt.reset()?; - entry.insert(chunk_data); - } - - let chunk_data = chunks - .get_mut(&chunk_index) - .expect("chunk must be loaded before partial write"); - if chunk_data.len() < offset_in_chunk + to_write { - chunk_data.resize(offset_in_chunk + to_write, 0); - } - chunk_data[offset_in_chunk..offset_in_chunk + to_write] - .copy_from_slice(write_slice); - - written += to_write; - } - } - - let chunks_written = chunks.len() as u64; - // Tier Three Axis H investigation: tried a multi-row VALUES batch - // with up to 32 rows per execute() but measured slower wall-time in - // 5-iter runs, suggesting libSQL doesn't share the - // prepared-statement cost reduction across different VALUES - // arities or that the per-execute setup cost dwarfed any saved - // round-trips on our workload sizes. Reverted to the cached - // single-row prepared statement. - for (chunk_index, chunk_data) in chunks { - insert_stmt - .execute((self.ino, chunk_index, Value::Blob(chunk_data))) - .await?; - insert_stmt.reset()?; - } - - crate::profiling::record_chunk_write_chunks(chunks_written); - Ok(()) - } -} - -impl AgentFS { - /// Create a new filesystem - pub async fn new(db_path: &str) -> Result { - let db = Builder::new_local(db_path).build().await?; - let pool = if db_path == ":memory:" { - ConnectionPool::new_single_connection(db) - } else { - ConnectionPool::with_options(db, file_backed_connection_pool_options()) - }; - let db_path = if db_path == ":memory:" { - None - } else { - Some(PathBuf::from(db_path)) - }; - Self::from_pool_with_path(pool, db_path).await - } - - /// Create a filesystem from a connection pool - pub async fn from_pool(pool: ConnectionPool) -> Result { - Self::from_pool_with_path(pool, None).await - } - - pub(crate) async fn from_pool_with_path( - pool: ConnectionPool, - db_path: Option, - ) -> Result { - let conn = pool.get_connection().await?; - - // Refuse legacy schemas before initialization so v0.4 databases are not - // silently mutated into v0.5. Copy migration is handled separately. - schema::check_schema_version(&conn).await?; - - // Initialize schema first - Self::initialize_schema(&conn).await?; - - // Get chunk_size from config (or use default) - let chunk_size = Self::read_chunk_size(&conn).await?; - let inline_threshold = Self::read_inline_threshold(&conn).await?; - - let attr_cache = Arc::new(AttrCache::new(ATTR_CACHE_MAX_SIZE)); - // Tier Three Axis D: default the SDK write batcher to ON, matching - // the cli's `FuseKernelCacheConfig::from_env` which defaults - // `AGENTFS_FUSE_WRITEBACK` to TRUE when unset. Tier Two shipped the - // cross-inode batched commit path but the env var defaulted to FALSE - // on this side, making A1 dead code under the canonical workload - // (see tier-two-post/COMPARISON.md retroactive correction). - let write_batcher = if env_flag_default(WRITE_BATCHER_ENABLE_ENV, true) { - Some(Arc::new(AgentFSWriteBatcher::from_env( - pool.clone(), - chunk_size, - inline_threshold, - attr_cache.clone(), - ))) - } else { - None - }; - - // Sweep POSIX orphans a crash stranded: nlink = 0 rows are files that - // were unlinked while open (reap deferred) and never reaped. They are - // invisible (no dentry), so deleting them before serving is safe. - conn.execute( - "DELETE FROM fs_data WHERE ino IN (SELECT ino FROM fs_inode WHERE nlink = 0)", - (), - ) - .await?; - conn.execute( - "DELETE FROM fs_symlink WHERE ino IN (SELECT ino FROM fs_inode WHERE nlink = 0)", - (), - ) - .await?; - conn.execute("DELETE FROM fs_inode WHERE nlink = 0", ()) - .await?; - - let overlay_reads = env_flag_default(OVERLAY_READS_ENV, true); - let fs = Self { - pool, - db_path: db_path.map(Arc::new), - chunk_size, - inline_threshold, - dentry_cache: Arc::new(DentryCache::new(DENTRY_CACHE_MAX_SIZE)), - negative_dentry_cache: Arc::new(NegativeDentryCache::new( - NEGATIVE_DENTRY_CACHE_MAX_SIZE, - )), - attr_cache, - write_batcher, - overlay_reads, - open_inodes: Arc::new(OpenInodes::default()), - _profile_report: Arc::new(crate::profiling::ProfileReportGuard::new("agentfs")), - }; - Ok(fs) - } - - /// Get the configured chunk size - pub fn chunk_size(&self) -> usize { - self.chunk_size - } - - /// Get the configured inline threshold. - pub fn inline_threshold(&self) -> usize { - self.inline_threshold - } - - /// Get a database connection from the pool - pub async fn get_connection(&self) -> Result { - self.pool.get_connection().await - } - - /// Get the connection pool - pub fn get_pool(&self) -> ConnectionPool { - self.pool.clone() - } - - /// Initialize the database schema - pub async fn initialize_schema(conn: &Connection) -> Result<()> { - // Create config table - conn.execute( - "CREATE TABLE IF NOT EXISTS fs_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL - )", - (), - ) - .await?; - - // Create inode table - conn.execute( - "CREATE TABLE IF NOT EXISTS fs_inode ( - ino INTEGER PRIMARY KEY AUTOINCREMENT, - mode INTEGER NOT NULL, - nlink INTEGER NOT NULL DEFAULT 0, - uid INTEGER NOT NULL DEFAULT 0, - gid INTEGER NOT NULL DEFAULT 0, - size INTEGER NOT NULL DEFAULT 0, - atime INTEGER NOT NULL, - mtime INTEGER NOT NULL, - ctime INTEGER NOT NULL, - rdev INTEGER NOT NULL DEFAULT 0, - data_inline BLOB, - storage_kind INTEGER NOT NULL DEFAULT 0 - )", - (), - ) - .await?; - - // Add nanosecond timestamp columns (backward compatible migration) - conn.execute( - "ALTER TABLE fs_inode ADD COLUMN atime_nsec INTEGER NOT NULL DEFAULT 0", - (), - ) - .await - .ok(); - conn.execute( - "ALTER TABLE fs_inode ADD COLUMN mtime_nsec INTEGER NOT NULL DEFAULT 0", - (), - ) - .await - .ok(); - conn.execute( - "ALTER TABLE fs_inode ADD COLUMN ctime_nsec INTEGER NOT NULL DEFAULT 0", - (), - ) - .await - .ok(); - conn.execute("ALTER TABLE fs_inode ADD COLUMN data_inline BLOB", ()) - .await - .ok(); - conn.execute( - "ALTER TABLE fs_inode ADD COLUMN storage_kind INTEGER NOT NULL DEFAULT 0", - (), - ) - .await - .ok(); - - // Create directory entry table - conn.execute( - "CREATE TABLE IF NOT EXISTS fs_dentry ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - parent_ino INTEGER NOT NULL, - ino INTEGER NOT NULL, - UNIQUE(parent_ino, name) - )", - (), - ) - .await?; - - // Create index for efficient path lookups - conn.execute( - "CREATE INDEX IF NOT EXISTS idx_fs_dentry_parent - ON fs_dentry(parent_ino, name)", - (), - ) - .await?; - - // Create data chunks table - conn.execute( - "CREATE TABLE IF NOT EXISTS fs_data ( - ino INTEGER NOT NULL, - chunk_index INTEGER NOT NULL, - data BLOB NOT NULL, - PRIMARY KEY (ino, chunk_index) - )", - (), - ) - .await?; - - // Create symlink table - conn.execute( - "CREATE TABLE IF NOT EXISTS fs_symlink ( - ino INTEGER PRIMARY KEY, - target TEXT NOT NULL - )", - (), - ) - .await?; - - // Ensure chunk_size config exists - let mut rows = conn - .query("SELECT value FROM fs_config WHERE key = 'chunk_size'", ()) - .await?; - - if rows.next().await?.is_none() { - conn.execute( - "INSERT INTO fs_config (key, value) VALUES ('chunk_size', ?)", - (DEFAULT_CHUNK_SIZE.to_string(),), - ) - .await?; - } - - // Ensure inline_threshold config exists - let mut rows = conn - .query( - "SELECT value FROM fs_config WHERE key = 'inline_threshold'", - (), - ) - .await?; - - if rows.next().await?.is_none() { - conn.execute( - "INSERT INTO fs_config (key, value) VALUES ('inline_threshold', ?)", - (DEFAULT_INLINE_THRESHOLD.to_string(),), - ) - .await?; - } - - // Set schema version - conn.execute( - "INSERT OR REPLACE INTO fs_config (key, value) VALUES ('schema_version', ?)", - [AGENTFS_SCHEMA_VERSION], - ) - .await?; - - // Ensure root directory exists with correct ownership - let mut rows = conn - .query("SELECT ino FROM fs_inode WHERE ino = ?", (ROOT_INO,)) - .await?; - - // SAFETY: getuid/getgid are always safe - #[cfg(unix)] - let (uid, gid) = unsafe { (libc::getuid(), libc::getgid()) }; - #[cfg(not(unix))] - let (uid, gid) = (0u32, 0u32); - - if rows.next().await?.is_none() { - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - conn.execute( - "INSERT INTO fs_inode (ino, mode, nlink, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec) - VALUES (?, ?, 2, ?, ?, 0, ?, ?, ?, ?, ?, ?)", - (ROOT_INO, DEFAULT_DIR_MODE as i64, uid, gid, now_secs, now_secs, now_secs, now_nsec, now_nsec, now_nsec), - ) - .await?; - } else { - // Update existing root inode ownership to current user - conn.execute( - "UPDATE fs_inode SET uid = ?, gid = ? WHERE ino = ?", - (uid, gid, ROOT_INO), - ) - .await?; - } - - Ok(()) - } - - /// Read chunk size from config - async fn read_chunk_size(conn: &Connection) -> Result { - let mut rows = conn - .query("SELECT value FROM fs_config WHERE key = 'chunk_size'", ()) - .await?; - - if let Some(row) = rows.next().await? { - let value = row - .get_value(0) - .ok() - .and_then(|v| match v { - Value::Text(s) => s.parse::().ok(), - Value::Integer(i) => Some(i as usize), - _ => None, - }) - .unwrap_or(DEFAULT_CHUNK_SIZE); - Ok(value) - } else { - Ok(DEFAULT_CHUNK_SIZE) - } - } - - /// Read inline threshold from config - async fn read_inline_threshold(conn: &Connection) -> Result { - let mut rows = conn - .query( - "SELECT value FROM fs_config WHERE key = 'inline_threshold'", - (), - ) - .await?; - - if let Some(row) = rows.next().await? { - let value = row - .get_value(0) - .ok() - .and_then(|v| match v { - Value::Text(s) => s.parse::().ok(), - Value::Integer(i) => Some(i as usize), - _ => None, - }) - .unwrap_or(DEFAULT_INLINE_THRESHOLD); - Ok(value) - } else { - Ok(DEFAULT_INLINE_THRESHOLD) - } - } - - /// Normalize a path - fn normalize_path(&self, path: &str) -> String { - let normalized = path.trim_end_matches('/'); - let normalized = if normalized.is_empty() { - "/" - } else if normalized.starts_with('/') { - normalized - } else { - return format!("/{}", normalized); - }; - - // Handle . and .. components - let components: Vec<&str> = normalized.split('/').filter(|s| !s.is_empty()).collect(); - let mut result = Vec::new(); - - for component in components { - match component { - "." => { - // Current directory - skip it - continue; - } - ".." => { - // Parent directory - only pop if there is a component to pop (don't traverse above root) - if !result.is_empty() { - result.pop(); - } - } - _ => { - result.push(component); - } - } - } - - if result.is_empty() { - "/".to_string() - } else { - format!("/{}", result.join("/")) - } - } - - /// Split path into components - fn split_path(&self, path: &str) -> Vec { - let normalized = self.normalize_path(path); - if normalized == "/" { - return vec![]; - } - normalized - .split('/') - .filter(|p| !p.is_empty()) - .map(|s| s.to_string()) - .collect() - } - - /// Look up a child entry by parent inode and name using a provided connection. - /// - /// This is more efficient than `resolve_path` when you already have the parent inode, - /// as it avoids re-resolving all parent path components. - async fn lookup_child( - &self, - conn: &Connection, - parent_ino: i64, - name: &str, - ) -> Result> { - if let Some(cached_ino) = self.dentry_cache.get(parent_ino, name) { - return Ok(Some(cached_ino)); - } - if self.negative_dentry_cache.contains(parent_ino, name) { - return Ok(None); - } - - let mut stmt = conn - .prepare_cached("SELECT ino FROM fs_dentry WHERE parent_ino = ? AND name = ?") - .await?; - let mut rows = stmt.query((parent_ino, name)).await?; - - let mut found_ino = None; - let mut row_count = 0; - - while let Some(row) = rows.next().await? { - found_ino = row.get_value(0).ok().and_then(|v| v.as_integer().copied()); - row_count += 1; - } - - if row_count > 1 { - return Err(FsError::InvalidPath.into()); - } - - if let Some(ino) = found_ino { - self.cache_dentry(parent_ino, name, ino); - } else { - self.cache_negative_dentry(parent_ino, name); - } - - Ok(found_ino) - } - - fn cache_attr(&self, stats: Stats) { - self.attr_cache.insert(stats); - } - - pub(crate) fn invalidate_attr(&self, ino: i64) { - self.attr_cache.remove(ino); - } - - /// Drain pending batched writes for one inode. - pub async fn drain_inode_writes(&self, ino: i64) -> Result<()> { - if let Some(batcher) = &self.write_batcher { - batcher - .drain_inode(ino, AgentFSWriteBatchDrainReason::Explicit) - .await?; - } - Ok(()) - } - - /// Prelude shared by chmod / chown / utimens. - /// - /// Legacy behaviour (`AGENTFS_DRAIN_ON_SETATTR=1`): synchronously commit - /// the inode's pending batched writes so the deferred data commit can - /// never re-stamp mtime/ctime after the explicit attribute change. With - /// FUSE writeback caching the kernel issues one SETATTR per written file, - /// so that drain serialised a SQLite commit per file on the clone path. - /// - /// Default: skip the drain and instead mark the pending entry so the - /// eventual batched commit preserves mtime/ctime (`mark_times_explicit` / - /// `preserve_times`). The mark happens BEFORE the caller's fs_inode - /// UPDATE; combined with the commit path re-reading the flag after it - /// holds the SQLite write lock, the explicitly-set attributes win in every - /// interleaving. - /// - /// The deferral requires Tier-4 overlay reads: with - /// `AGENTFS_OVERLAY_READS=0`, getattr/size are served straight from - /// SQLite with no pending-size merge, so the legacy drain is kept to make - /// the just-written size visible at close time (git reads files by - /// `st_size`). - async fn prepare_attr_change(&self, ino: i64) -> Result<()> { - if drain_on_setattr() || !self.overlay_reads { - return self.drain_inode_writes(ino).await; - } - if let Some(batcher) = &self.write_batcher { - batcher.mark_times_explicit(ino); - } - Ok(()) - } - - /// Tier Four helper: merge the batcher's pending state into a `Stats` row - /// read from SQLite, so callers that hold a pool connection don't need to - /// drain (which would deadlock on single-conn pools): - /// - `size` is OR-ed with the pending max write end (mirrors the logic in - /// `AgentFS::getattr` and `AgentFSFile::pread`); - /// - explicitly-set times stashed by `utimens` (`PendingTimeChange`) are - /// overlaid so a deferred SETATTR is visible before its drain commits. - /// - /// Fast-paths when the batcher has nothing pending for this inode (Tier 4 - /// read hot path: most reads pay zero cost beyond a read-lock HashMap hit). - fn merge_pending_view(&self, ino: i64, stats: Option<&mut Stats>) { - let Some(stats) = stats else { - return; - }; - // Escape hatch: when overlay reads are disabled, callers' SQLite - // size view is already authoritative because pwrites went straight - // to SQLite (see AgentFSFile::pwrite) and utimens never stashes. - // No merge needed. - if !self.overlay_reads { - return; - } - let Some(batcher) = &self.write_batcher else { - return; - }; - if let Some(times) = batcher.peek_pending_times(ino) { - times.merge_into(stats); - } - if !batcher.has_pending(ino) { - return; - } - if let Some(pending_end) = batcher.peek_pending_max_end(ino) { - let pending_end_i64 = i64::try_from(pending_end).unwrap_or(i64::MAX); - if pending_end_i64 > stats.size { - stats.size = pending_end_i64; - } - } - } - - /// Drain all pending batched writes for this AgentFS instance. - pub async fn drain_all(&self) -> Result<()> { - if let Some(batcher) = &self.write_batcher { - batcher - .drain_all(AgentFSWriteBatchDrainReason::Explicit) - .await?; - } - let conn = self.pool.get_connection().await?; - checkpoint_wal(&conn).await?; - Ok(()) - } - - /// Drain all writes and leave the database in single-file journal mode for clean shutdown. - pub async fn finalize(&self) -> Result<()> { - self.process_deferred_reaps().await?; - self.drain_all().await?; - if let Some(path) = &self.db_path { - remove_checkpointed_sidecars(path.as_ref())?; - } - Ok(()) - } - - /// Reap inodes whose deletion unlink/rename deferred because open - /// handles existed (POSIX unlink-while-open). Runs opportunistically at - /// namespace mutations and at finalize; a crash is covered by the - /// nlink=0 sweep at mount. - pub async fn process_deferred_reaps(&self) -> Result<()> { - if !self.open_inodes.has_pending_reaps() { - return Ok(()); - } - let inos = self.open_inodes.take_reap_queue(); - let conn = self.pool.get_connection().await?; - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let result: Result<()> = async { - for ino in &inos { - // The nlink=0 guard makes a stale queue entry (row already - // reaped, or the rowid reused by a live file) a no-op. - let changed = conn - .execute("DELETE FROM fs_inode WHERE ino = ? AND nlink = 0", (*ino,)) - .await?; - if changed == 0 { - continue; - } - if let Some(batcher) = &self.write_batcher { - batcher.discard_pending(*ino); - } - conn.execute("DELETE FROM fs_data WHERE ino = ?", (*ino,)) - .await?; - conn.execute("DELETE FROM fs_symlink WHERE ino = ?", (*ino,)) - .await?; - } - Ok(()) - } - .await; - match result { - Ok(()) => { - txn.commit().await?; - for ino in &inos { - self.invalidate_attr(*ino); - } - Ok(()) - } - Err(error) => { - let _ = txn.rollback().await; - self.open_inodes.requeue_reaps(inos); - Err(error) - } - } - } - - fn invalidate_parent_attr(&self, parent_ino: i64) { - self.invalidate_attr(parent_ino); - } - - fn invalidate_dentry(&self, parent_ino: i64, name: &str) { - self.dentry_cache.remove(parent_ino, name); - self.negative_dentry_cache.remove(parent_ino, name); - } - - fn cache_dentry(&self, parent_ino: i64, name: &str, child_ino: i64) { - self.negative_dentry_cache.remove(parent_ino, name); - self.dentry_cache.insert(parent_ino, name, child_ino); - } - - fn cache_negative_dentry(&self, parent_ino: i64, name: &str) { - self.dentry_cache.remove(parent_ino, name); - self.negative_dentry_cache.insert(parent_ino, name); - } - - /// Get link count for an inode - async fn get_link_count(&self, conn: &Connection, ino: i64) -> Result { - let mut stmt = conn - .prepare_cached("SELECT nlink FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if let Some(row) = rows.next().await? { - let nlink = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - Ok(nlink as u32) - } else { - Ok(0) - } - } - - /// Get file attributes by inode using an existing connection - async fn getattr_with_conn(&self, conn: &Connection, ino: i64) -> Result> { - if let Some(stats) = self.attr_cache.get(ino) { - return Ok(Some(stats)); - } - - let mut stmt = conn - .prepare_cached("SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime, rdev, atime_nsec, mtime_nsec, ctime_nsec FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if let Some(row) = rows.next().await? { - let stats = Self::build_stats_from_row(&row)?; - self.cache_attr(stats.clone()); - Ok(Some(stats)) - } else { - Ok(None) - } - } - - /// Build a Stats object from a database row - /// - /// The row should contain columns in this order: - /// ino, mode, nlink, uid, gid, size, atime, mtime, ctime - fn build_stats_from_row(row: &turso::Row) -> Result { - Ok(Stats { - ino: row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - mode: row - .get_value(1) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - nlink: row - .get_value(2) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(1) as u32, - uid: row - .get_value(3) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - gid: row - .get_value(4) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - size: row - .get_value(5) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - atime: row - .get_value(6) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - mtime: row - .get_value(7) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - ctime: row - .get_value(8) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - atime_nsec: row - .get_value(10) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - mtime_nsec: row - .get_value(11) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - ctime_nsec: row - .get_value(12) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - rdev: row - .get_value(9) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u64, - }) - } - - /// Resolve a path to an inode number - async fn resolve_path(&self, path: &str) -> Result> { - let conn = self.pool.get_connection().await?; - self.resolve_path_with_conn(&conn, path).await - } - - /// Resolve a path to an inode number using a provided connection - async fn resolve_path_with_conn(&self, conn: &Connection, path: &str) -> Result> { - let components = self.split_path(path); - crate::profiling::record_path_resolution(components.len() as u64); - if components.is_empty() { - return Ok(Some(ROOT_INO)); - } - - let mut statement: Option = None; - let mut current_ino = ROOT_INO; - for component in components { - // Check cache first - if let Some(cached_ino) = self.dentry_cache.get(current_ino, &component) { - current_ino = cached_ino; - continue; - } - if self.negative_dentry_cache.contains(current_ino, &component) { - crate::profiling::record_negative_lookup(); - return Ok(None); - } - - // Cache miss - query database - if let Some(statement) = &mut statement { - statement.reset()?; - } else { - statement = Some( - conn.prepare_cached( - "SELECT ino FROM fs_dentry WHERE parent_ino = ? AND name = ?", - ) - .await?, - ); - } - let statement = statement.as_mut().expect("statement was set above"); - let mut rows = statement.query((current_ino, component.as_str())).await?; - - let mut found_row = None; - let mut row_count = 0; - - while let Some(row) = rows.next().await? { - found_row = Some(row); - row_count += 1; - } - - if row_count > 1 { - return Err(FsError::InvalidPath.into()); - } - - if let Some(row) = found_row { - let child_ino = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - - // Populate cache - self.cache_dentry(current_ino, &component, child_ino); - current_ino = child_ino; - } else { - crate::profiling::record_negative_lookup(); - self.cache_negative_dentry(current_ino, &component); - return Ok(None); - } - } - - Ok(Some(current_ino)) - } - - /// Get file statistics without following symlinks - pub async fn lstat(&self, path: &str) -> Result> { - let conn = self.pool.get_connection().await?; - let path = self.normalize_path(path); - let ino = match self.resolve_path_with_conn(&conn, &path).await? { - Some(ino) => ino, - None => return Ok(None), - }; - - // Tier Four: don't drain while holding the conn (would deadlock on - // single-conn pools and starve under contention on larger pools). - // Read SQLite, then OR in pending writes' max-end. - let mut stats = self.getattr_with_conn(&conn, ino).await?; - self.merge_pending_view(ino, stats.as_mut()); - Ok(stats) - } - - /// Get file statistics, following symlinks - pub async fn stat(&self, path: &str) -> Result> { - let conn = self.pool.get_connection().await?; - let path = self.normalize_path(path); - - // Follow symlinks with a maximum depth to prevent infinite loops - let mut current_path = path; - let max_symlink_depth = 40; // Standard limit for symlink following - - for _ in 0..max_symlink_depth { - let ino = match self.resolve_path_with_conn(&conn, ¤t_path).await? { - Some(ino) => ino, - None => return Ok(None), - }; - // Tier Four: see lstat — no drain while holding conn. - if let Some(mut stats) = self.getattr_with_conn(&conn, ino).await? { - // Check if this is a symlink - if (stats.mode & S_IFMT) == S_IFLNK { - // Read the symlink target - let target = self - .readlink_with_conn(&conn, ¤t_path) - .await? - .ok_or(FsError::NotFound)?; - - // Resolve target path (handle both absolute and relative paths) - current_path = if target.starts_with('/') { - target - } else { - // Relative path - resolve relative to the symlink's directory - let base_path = Path::new(¤t_path); - let parent = base_path.parent().unwrap_or(Path::new("/")); - let joined = parent.join(&target); - joined.to_string_lossy().into_owned() - }; - current_path = self.normalize_path(¤t_path); - continue; // Follow the symlink - } - - self.merge_pending_view(ino, Some(&mut stats)); - return Ok(Some(stats)); - } else { - return Ok(None); - } - } - - // Too many symlinks - Err(FsError::SymlinkLoop.into()) - } - - /// Get file statistics, following symlinks (using provided connection) - async fn stat_with_conn(&self, conn: &Connection, path: &str) -> Result> { - let path = self.normalize_path(path); - - // Follow symlinks with a maximum depth to prevent infinite loops - let mut current_path = path; - let max_symlink_depth = 40; // Standard limit for symlink following - - for _ in 0..max_symlink_depth { - let ino = match self.resolve_path_with_conn(conn, ¤t_path).await? { - Some(ino) => ino, - None => return Ok(None), - }; - - if let Some(stats) = self.getattr_with_conn(conn, ino).await? { - // Check if this is a symlink - if (stats.mode & S_IFMT) == S_IFLNK { - // Read the symlink target - let target = self - .readlink_with_conn(conn, ¤t_path) - .await? - .ok_or(FsError::InvalidPath)?; - - // Resolve target path (handle both absolute and relative paths) - current_path = if target.starts_with('/') { - target - } else { - // Relative path - resolve relative to the symlink's directory - let base_path = Path::new(¤t_path); - let parent = base_path.parent().unwrap_or(Path::new("/")); - let joined = parent.join(&target); - joined.to_string_lossy().into_owned() - }; - current_path = self.normalize_path(¤t_path); - continue; // Follow the symlink - } - - // Not a symlink, return the stats - return Ok(Some(stats)); - } else { - return Ok(None); - } - } - - // Too many symlinks - Err(FsError::SymlinkLoop.into()) - } - - /// Bulk-import a tree of nodes under `dest_parent` using large - /// multi-inode transactions instead of one transaction per node, sized by - /// the write batcher's txn limits (`AGENTFS_BATCH_TXN_INODES` / - /// `AGENTFS_BATCH_TXN_BYTES`). This is the fast path for populating the - /// database without per-file FUSE round trips (`agentfs clone` / `fs - /// import`): a 4.7k-file worktree pays a handful of commits instead of - /// ~9.4k per-file create+write transaction boundaries. - /// - /// Entries must be ordered parents-before-children; every parent - /// directory of a nested path must itself appear as an entry (or be the - /// import root). All inodes are stamped with `opts.timestamp`, and the - /// returned rows echo the exact `ino`/`mode`/`size` the filesystem will - /// serve, so callers can fabricate externally-consistent stat metadata - /// (e.g. a git index) without re-reading anything. - pub async fn import_entries( - &self, - dest_parent: i64, - entries: &[ImportEntry], - opts: &ImportOptions, - ) -> Result> { - let mut session = self.begin_import(dest_parent, opts.clone()).await?; - session.import_chunk(entries).await?; - Ok(session.finish()) - } - - /// Begin a streaming bulk import under `dest_parent`; see - /// [`ImportSession`]. [`AgentFS::import_entries`] is the buffered - /// one-shot form. - pub async fn begin_import( - &self, - dest_parent: i64, - opts: ImportOptions, - ) -> Result { - Ok(ImportSession { - fs: self.clone(), - conn: self.pool.get_connection().await?, - dest_parent, - opts, - dir_inos: HashMap::new(), - results: Vec::new(), - }) - } - - /// One chunk of a streaming import. `conn`, `dir_inos`, and `results` - /// persist across calls so later chunks may reference directories - /// imported by earlier ones; each call still splits its entries into - /// bounded transactions. - async fn import_chunk_with_conn( - &self, - conn: &crate::connection_pool::PooledConnection, - dest_parent: i64, - opts: &ImportOptions, - dir_inos: &mut HashMap, - results: &mut Vec, - entries: &[ImportEntry], - ) -> Result<()> { - let max_inodes = env_usize(WRITE_BATCHER_TXN_INODES_ENV, 1024).max(1); - let max_bytes = env_usize(WRITE_BATCHER_TXN_BYTES_ENV, 32 * 1024 * 1024).max(1); - let (ts_secs, ts_nsec) = opts.timestamp; - - let mut inode_stmt = conn - .prepare_cached( - "INSERT INTO fs_inode (mode, nlink, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec, data_inline, storage_kind) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING ino", - ) - .await?; - let mut dentry_stmt = conn - .prepare_cached("INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)") - .await?; - let mut chunk_stmt = conn - .prepare_cached("INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?)") - .await?; - let mut symlink_stmt = conn - .prepare_cached("INSERT INTO fs_symlink (ino, target) VALUES (?, ?)") - .await?; - let mut parent_stmt = conn - .prepare_cached( - "UPDATE fs_inode SET nlink = nlink + ?, ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", - ) - .await?; - - results.reserve(entries.len()); - - let mut idx = 0usize; - while idx < entries.len() { - let mut batch_end = idx; - let mut batch_bytes = 0usize; - while batch_end < entries.len() - && batch_end - idx < max_inodes - && (batch_end == idx || batch_bytes + entries[batch_end].data.len() <= max_bytes) - { - batch_bytes += entries[batch_end].data.len(); - batch_end += 1; - } - - // Cache fills staged until after a successful commit so a rolled - // back batch never leaves phantom dentries/attrs behind. - let mut staged: Vec<(i64, String, Stats)> = Vec::with_capacity(batch_end - idx); - // parent ino -> nlink bump from new subdirectories (".."). - let mut parent_bumps: HashMap = HashMap::new(); - - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - for entry in &entries[idx..batch_end] { - let (parent_path, name) = match entry.path.rsplit_once('/') { - Some((parent, name)) => (parent, name), - None => ("", entry.path.as_str()), - }; - if name.is_empty() || name == "." || name == ".." { - return Err(FsError::InvalidPath.into()); - } - if name.len() > MAX_NAME_LEN { - return Err(FsError::NameTooLong.into()); - } - let parent_ino = if parent_path.is_empty() { - dest_parent - } else { - *dir_inos - .get(parent_path) - .ok_or_else(|| Error::Fs(FsError::NotFound))? - }; - - let kind = entry.mode & S_IFMT; - let (nlink, size, data_inline, storage_kind) = match kind { - S_IFDIR => (2i64, 0u64, Value::Null, STORAGE_CHUNKED), - S_IFLNK => (1, entry.data.len() as u64, Value::Null, STORAGE_CHUNKED), - S_IFREG => { - if entry.data.len() <= self.inline_threshold { - ( - 1, - entry.data.len() as u64, - Value::Blob(entry.data.clone()), - STORAGE_INLINE, - ) - } else { - (1, entry.data.len() as u64, Value::Null, STORAGE_CHUNKED) - } - } - _ => return Err(FsError::InvalidPath.into()), - }; - - let row = inode_stmt - .query_row(( - entry.mode as i64, - nlink, - opts.uid, - opts.gid, - size as i64, - ts_secs, - ts_secs, - ts_secs, - ts_nsec, - ts_nsec, - ts_nsec, - data_inline, - storage_kind, - )) - .await?; - let ino = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; - - match dentry_stmt.execute((name, parent_ino, ino)).await { - Ok(_) => {} - Err(turso::Error::Constraint(_)) => return Err(FsError::AlreadyExists.into()), - Err(error) => return Err(error.into()), - } - - match kind { - S_IFDIR => { - dir_inos.insert(entry.path.clone(), ino); - *parent_bumps.entry(parent_ino).or_insert(0) += 1; - } - S_IFLNK => { - let target = std::str::from_utf8(&entry.data) - .map_err(|_| Error::Fs(FsError::InvalidPath))?; - symlink_stmt.execute((ino, target)).await?; - parent_bumps.entry(parent_ino).or_insert(0); - } - _ => { - if storage_kind == STORAGE_CHUNKED { - for (chunk_index, chunk) in - entry.data.chunks(self.chunk_size).enumerate() - { - chunk_stmt - .execute((ino, chunk_index as i64, Value::Blob(chunk.to_vec()))) - .await?; - } - } - parent_bumps.entry(parent_ino).or_insert(0); - } - } - - staged.push(( - parent_ino, - name.to_string(), - Stats { - ino, - mode: entry.mode, - nlink: nlink as u32, - uid: opts.uid, - gid: opts.gid, - size: size as i64, - atime: ts_secs, - mtime: ts_secs, - ctime: ts_secs, - atime_nsec: ts_nsec as u32, - mtime_nsec: ts_nsec as u32, - ctime_nsec: ts_nsec as u32, - rdev: 0, - }, - )); - results.push(ImportedEntry { - path: entry.path.clone(), - ino, - mode: entry.mode, - size, - }); - } - - for (parent_ino, bump) in &parent_bumps { - parent_stmt - .execute((*bump, ts_secs, ts_secs, ts_nsec, ts_nsec, *parent_ino)) - .await?; - } - - txn.commit().await?; - crate::profiling::record_agentfs_batcher_commit_txn(staged.len() as u64); - - for (parent_ino, name, stats) in staged { - self.cache_dentry(parent_ino, &name, stats.ino); - // Directories keep changing (nlink/time bumps as later batches - // add children), so only leaf attrs are safe to prime. - if stats.mode & S_IFMT != S_IFDIR { - self.cache_attr(stats); - } - } - for parent_ino in parent_bumps.keys() { - self.invalidate_attr(*parent_ino); - } - - idx = batch_end; - } - - Ok(()) - } - - /// Create a directory - pub async fn mkdir(&self, path: &str, uid: u32, gid: u32) -> Result<()> { - let conn = self.pool.get_connection().await?; - let path = self.normalize_path(path); - let components = self.split_path(&path); - - if components.is_empty() { - return Err(FsError::RootOperation.into()); - } - - let parent_path = if components.len() == 1 { - "/".to_string() - } else { - format!("/{}", components[..components.len() - 1].join("/")) - }; - - let parent_ino = self - .resolve_path_with_conn(&conn, &parent_path) - .await? - .ok_or(FsError::NotFound)?; - - let name = components.last().unwrap(); - - // Check if already exists (single query using parent_ino we already have) - if self.lookup_child(&conn, parent_ino, name).await?.is_some() { - return Err(FsError::AlreadyExists.into()); - } - - // Create inode with default directory mode (path-based API doesn't accept mode) - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - let mut stmt = conn - .prepare_cached( - "INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec) - VALUES (?, ?, ?, 0, ?, ?, ?, ?, ?, ?) RETURNING ino", - ) - .await?; - let row = stmt - .query_row(( - DEFAULT_DIR_MODE as i64, - uid, - gid, - now_secs, - now_secs, - now_secs, - now_nsec, - now_nsec, - now_nsec, - )) - .await?; - - let ino = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; - - // Create directory entry - let mut stmt = conn - .prepare_cached("INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)") - .await?; - stmt.execute((name.as_str(), parent_ino, ino)).await?; - - // Set nlink to 2 for new directory (self "." + parent's dentry) - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET nlink = 2 WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - - // Increment parent nlink (new directory's ".." link) and update timestamps - let mut stmt = conn - .prepare_cached( - "UPDATE fs_inode SET nlink = nlink + 1, ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", - ) - .await?; - stmt.execute((now_secs, now_secs, now_nsec, now_nsec, parent_ino)) - .await?; - - // Populate dentry cache - self.cache_dentry(parent_ino, name, ino); - self.invalidate_parent_attr(parent_ino); - - Ok(()) - } - - /// Create a special file node (FIFO, device, socket, or regular file) - pub async fn mknod(&self, path: &str, mode: u32, rdev: u64, uid: u32, gid: u32) -> Result<()> { - let conn = self.pool.get_connection().await?; - let path = self.normalize_path(path); - let components = self.split_path(&path); - - if components.is_empty() { - return Err(FsError::RootOperation.into()); - } - - let parent_path = if components.len() == 1 { - "/".to_string() - } else { - format!("/{}", components[..components.len() - 1].join("/")) - }; - - let parent_ino = self - .resolve_path_with_conn(&conn, &parent_path) - .await? - .ok_or(FsError::NotFound)?; - - let name = components.last().unwrap(); - - // Check if already exists - if self.lookup_child(&conn, parent_ino, name).await?.is_some() { - return Err(FsError::AlreadyExists.into()); - } - - // Create inode with mode and rdev - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - let mut stmt = conn - .prepare_cached( - "INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime, rdev, atime_nsec, mtime_nsec, ctime_nsec) - VALUES (?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?) RETURNING ino", - ) - .await?; - let row = stmt - .query_row(( - mode as i64, - uid, - gid, - now_secs, - now_secs, - now_secs, - rdev as i64, - now_nsec, - now_nsec, - now_nsec, - )) - .await?; - - let ino = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; - - // Create directory entry - let mut stmt = conn - .prepare_cached("INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)") - .await?; - stmt.execute((name.as_str(), parent_ino, ino)).await?; - - // Increment link count - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - - // Populate dentry cache - self.cache_dentry(parent_ino, name, ino); - self.invalidate_parent_attr(parent_ino); - - Ok(()) - } - - /// Create a new empty file with the specified mode and ownership. - /// - /// This is an optimized path for FUSE create() that combines inode creation, - /// dentry creation, and file handle opening in a single operation. - /// Returns both Stats and an open file handle. - pub async fn create_file( - &self, - path: &str, - mode: u32, - uid: u32, - gid: u32, - ) -> Result<(Stats, BoxedFile)> { - let conn = self.pool.get_connection().await?; - let path = self.normalize_path(path); - let components = self.split_path(&path); - - if components.is_empty() { - return Err(FsError::RootOperation.into()); - } - - let parent_path = match components.len() { - 1 => "/".to_string(), - _ => format!("/{}", components[..components.len() - 1].join("/")), - }; - - let parent_ino = self - .resolve_path_with_conn(&conn, &parent_path) - .await? - .ok_or(FsError::NotFound)?; - - let name = components.last().unwrap(); - - if self.lookup_child(&conn, parent_ino, name).await?.is_some() { - return Err(FsError::AlreadyExists.into()); - } - - // Prepare statements before starting the transaction - let mut inode_stmt = conn - .prepare_cached( - "INSERT INTO fs_inode (mode, nlink, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec, data_inline, storage_kind) - VALUES (?, 1, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING ino", - ) - .await?; - let mut dentry_stmt = conn - .prepare_cached("INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)") - .await?; - - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - let file_mode = S_IFREG | (mode & 0o7777); - - let row = inode_stmt - .query_row(( - file_mode as i64, - uid, - gid, - now_secs, - now_secs, - now_secs, - now_nsec, - now_nsec, - now_nsec, - Value::Blob(Vec::new()), - STORAGE_INLINE, - )) - .await?; - - let ino = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; - - dentry_stmt - .execute((name.as_str(), parent_ino, ino)) - .await?; - - txn.commit().await?; - - self.cache_dentry(parent_ino, name, ino); - self.invalidate_parent_attr(parent_ino); - - let stats = Stats { - ino, - mode: file_mode, - nlink: 1, - uid, - gid, - size: 0, - atime: now_secs, - mtime: now_secs, - ctime: now_secs, - atime_nsec: now_nsec as u32, - mtime_nsec: now_nsec as u32, - ctime_nsec: now_nsec as u32, - rdev: 0, - }; - self.cache_attr(stats.clone()); - - let file: BoxedFile = Arc::new(AgentFSFile { - pool: self.pool.clone(), - ino, - chunk_size: self.chunk_size, - inline_threshold: self.inline_threshold, - attr_cache: self.attr_cache.clone(), - write_batcher: self.write_batcher.clone(), - overlay_reads: self.overlay_reads, - _open_guard: Some(self.open_inodes.guard(ino)), - }); - - Ok((stats, file)) - } - - /// Read data from a file - pub async fn read_file(&self, path: &str) -> Result>> { - let conn = self.pool.get_connection().await?; - let ino = match self.resolve_path_with_conn(&conn, path).await? { - Some(ino) => ino, - None => return Ok(None), - }; - drop(conn); - self.drain_inode_writes(ino).await?; - let conn = self.pool.get_connection().await?; - - let file = AgentFSFile { - pool: self.pool.clone(), - ino, - chunk_size: self.chunk_size, - inline_threshold: self.inline_threshold, - attr_cache: self.attr_cache.clone(), - write_batcher: self.write_batcher.clone(), - overlay_reads: self.overlay_reads, - _open_guard: None, - }; - Ok(Some(file.read_inode_with_conn(&conn, 0, u64::MAX).await?)) - } - - /// Reads from a file at a given offset. - /// - /// Similar to POSIX `pread`, this reads up to `size` bytes from the file - /// starting at `offset`, without modifying any file cursor. - /// - /// Returns `Ok(None)` if the file does not exist. - pub async fn pread(&self, path: &str, offset: u64, size: u64) -> Result>> { - let conn = self.pool.get_connection().await?; - let ino = match self.resolve_path_with_conn(&conn, path).await? { - Some(ino) => ino, - None => return Ok(None), - }; - drop(conn); - self.drain_inode_writes(ino).await?; - let conn = self.pool.get_connection().await?; - - let file = AgentFSFile { - pool: self.pool.clone(), - ino, - chunk_size: self.chunk_size, - inline_threshold: self.inline_threshold, - attr_cache: self.attr_cache.clone(), - write_batcher: self.write_batcher.clone(), - overlay_reads: self.overlay_reads, - _open_guard: None, - }; - Ok(Some(file.read_inode_with_conn(&conn, offset, size).await?)) - } - - /// Writes to a file at a given offset. - /// - /// Similar to POSIX `pwrite`, this writes `data` to the file starting at - /// `offset`, without modifying any file cursor. - /// - /// If the offset is beyond the current file size, the file is extended with zeros. - /// If the file does not exist, it will be created. - pub async fn pwrite(&self, path: &str, offset: u64, data: &[u8]) -> Result<()> { - let conn = self.pool.get_connection().await?; - let path = self.normalize_path(path); - let components = self.split_path(&path); - - if components.is_empty() { - return Err(FsError::RootOperation.into()); - } - - let parent_path = if components.len() == 1 { - "/".to_string() - } else { - format!("/{}", components[..components.len() - 1].join("/")) - }; - - let parent_ino = self - .resolve_path_with_conn(&conn, &parent_path) - .await? - .ok_or(FsError::NotFound)?; - - let name = components.last().unwrap(); - - let existing_ino = self.resolve_path_with_conn(&conn, &path).await?; - drop(conn); - if let Some(existing_ino) = existing_ino { - self.drain_inode_writes(existing_ino).await?; - } - let conn = self.pool.get_connection().await?; - - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - - let result: Result<(i64, bool)> = async { - // Get or create the inode - let (ino, created) = if let Some(ino) = self.resolve_path_with_conn(&conn, &path).await? { - (ino, false) - } else { - let (now_secs, now_nsec) = current_timestamp()?; - let mut stmt = conn - .prepare_cached( - "INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime, nlink, atime_nsec, mtime_nsec, ctime_nsec, data_inline, storage_kind) - VALUES (?, 0, 0, 0, ?, ?, ?, 1, ?, ?, ?, ?, ?) RETURNING ino", - ) - .await?; - let row = stmt - .query_row(( - DEFAULT_FILE_MODE as i64, - now_secs, - now_secs, - now_secs, - now_nsec, - now_nsec, - now_nsec, - Value::Blob(Vec::new()), - STORAGE_INLINE, - )) - .await?; - - let ino = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; - - let mut stmt = conn - .prepare_cached("INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)") - .await?; - stmt.execute((name.as_str(), parent_ino, ino)).await?; - (ino, true) - }; - - if data.is_empty() { - let (now_secs, now_nsec) = current_timestamp()?; - conn.prepare_cached("UPDATE fs_inode SET mtime = ?, mtime_nsec = ? WHERE ino = ?") - .await? - .execute((now_secs, now_nsec, ino)) - .await?; - return Ok((ino, created)); - } - - let file = AgentFSFile { - pool: self.pool.clone(), - ino, - chunk_size: self.chunk_size, - inline_threshold: self.inline_threshold, - attr_cache: self.attr_cache.clone(), - write_batcher: self.write_batcher.clone(), - overlay_reads: self.overlay_reads, - _open_guard: None, - }; - let ranges = [WriteRangeRef { offset, data }]; - file.pwrite_ranges_inode_with_conn(&conn, &ranges, false, None) - .await?; - - Ok((ino, created)) - } - .await; - - match result { - Ok((ino, created)) => { - txn.commit().await?; - self.invalidate_attr(ino); - if created { - self.cache_dentry(parent_ino, name, ino); - self.invalidate_parent_attr(parent_ino); - } - Ok(()) - } - Err(e) => { - let _ = txn.rollback().await; - Err(e) - } - } - } - - /// Truncate a file to a specific size. - /// - /// This operates directly on chunks without loading the entire file into memory: - /// - Shrinking: deletes chunks beyond new size, truncates the last chunk if needed - /// - Extending: pads with zeros up to the new size - pub async fn truncate(&self, path: &str, new_size: u64) -> Result<()> { - let conn = self.pool.get_connection().await?; - let path = self.normalize_path(path); - let ino = self - .resolve_path_with_conn(&conn, &path) - .await? - .ok_or(FsError::NotFound)?; - drop(conn); - self.drain_inode_writes(ino).await?; - let conn = self.pool.get_connection().await?; - - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let file = AgentFSFile { - pool: self.pool.clone(), - ino, - chunk_size: self.chunk_size, - inline_threshold: self.inline_threshold, - attr_cache: self.attr_cache.clone(), - write_batcher: self.write_batcher.clone(), - overlay_reads: self.overlay_reads, - _open_guard: None, - }; - let result = file.truncate_inode_with_conn(&conn, new_size).await; - - match result { - Ok(()) => { - txn.commit().await?; - self.invalidate_attr(ino); - Ok(()) - } - Err(e) => { - let _ = txn.rollback().await; - Err(e) - } - } - } - - /// List directory contents - pub async fn readdir(&self, ino: i64) -> Result>> { - let conn = self.pool.get_connection().await?; - let mut rows = conn - .query( - "SELECT name FROM fs_dentry WHERE parent_ino = ? ORDER BY name", - (ino,), - ) - .await?; - - let mut entries = Vec::new(); - while let Some(row) = rows.next().await? { - let name = row - .get_value(0) - .ok() - .and_then(|v| { - if let Value::Text(s) = v { - Some(s.clone()) - } else { - None - } - }) - .unwrap_or_default(); - if !name.is_empty() { - entries.push(name); - } - } - - Ok(Some(entries)) - } - - /// List directory contents with full statistics (optimized batch query) - /// - /// Returns entries with their stats in a single JOIN query, avoiding N+1 queries. - pub async fn readdir_plus(&self, ino: i64) -> Result>> { - let conn = self.pool.get_connection().await?; - let mut stmt = conn.prepare_cached("SELECT d.name, i.ino, i.mode, i.nlink, i.uid, i.gid, i.size, i.atime, i.mtime, i.ctime, i.rdev, i.atime_nsec, i.mtime_nsec, i.ctime_nsec - FROM fs_dentry d - JOIN fs_inode i ON d.ino = i.ino - WHERE d.parent_ino = ? - ORDER BY d.name" - ).await?; - // Single JOIN query to get all entry names and their stats (including link count) - let mut rows = stmt.query((ino,)).await?; - - let mut entries = Vec::new(); - while let Some(row) = rows.next().await? { - let name = row - .get_value(0) - .ok() - .and_then(|v| { - if let Value::Text(s) = v { - Some(s.clone()) - } else { - None - } - }) - .unwrap_or_default(); - - if name.is_empty() { - continue; - } - - let entry_ino = row - .get_value(1) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - - let nlink = row - .get_value(3) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(1) as u32; - - let stats = Stats { - ino: entry_ino, - mode: row - .get_value(2) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - nlink, - uid: row - .get_value(4) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - gid: row - .get_value(5) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - size: row - .get_value(6) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - atime: row - .get_value(7) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - mtime: row - .get_value(8) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - ctime: row - .get_value(9) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - atime_nsec: row - .get_value(11) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - mtime_nsec: row - .get_value(12) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - ctime_nsec: row - .get_value(13) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - rdev: row - .get_value(10) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u64, - }; - - self.cache_attr(stats.clone()); - entries.push(DirEntry { name, stats }); - } - - Ok(Some(entries)) - } - - /// Create a symbolic link with the specified ownership - pub async fn symlink(&self, target: &str, linkpath: &str, uid: u32, gid: u32) -> Result<()> { - let conn = self.pool.get_connection().await?; - let linkpath = self.normalize_path(linkpath); - let components = self.split_path(&linkpath); - - if components.is_empty() { - return Err(FsError::RootOperation.into()); - } - - // Get parent directory - let parent_path = if components.len() == 1 { - "/".to_string() - } else { - format!("/{}", components[..components.len() - 1].join("/")) - }; - - let parent_ino = self - .resolve_path_with_conn(&conn, &parent_path) - .await? - .ok_or(FsError::NotFound)?; - - let name = components.last().unwrap(); - - // Check if entry already exists (single query using parent_ino we already have) - if self.lookup_child(&conn, parent_ino, name).await?.is_some() { - return Err(FsError::AlreadyExists.into()); - } - - // Create inode for symlink - let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - - let mode = S_IFLNK | 0o777; // Symlinks typically have 777 permissions - let size = target.len() as i64; - - let mut stmt = conn - .prepare_cached( - "INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING ino", - ) - .await?; - let row = stmt - .query_row(( - mode, uid, gid, size, now_secs, now_secs, now_secs, now_nsec, now_nsec, now_nsec, - )) - .await?; - - // Get the newly created inode - let ino = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - - // Store symlink target - conn.execute( - "INSERT INTO fs_symlink (ino, target) VALUES (?, ?)", - (ino, target), - ) - .await?; - - // Create directory entry - conn.execute( - "INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)", - (name.as_str(), parent_ino, ino), - ) - .await?; - - // Increment link count - conn.execute( - "UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?", - (ino,), - ) - .await?; - - // Populate dentry cache - self.cache_dentry(parent_ino, name, ino); - self.invalidate_parent_attr(parent_ino); - - Ok(()) - } - - /// Create a hard link - /// - /// Creates a new directory entry `newpath` that refers to the same inode as `oldpath`. - /// Both paths will share the same file data and metadata (except for the name). - /// The link count (nlink) of the inode is incremented. - pub async fn link(&self, oldpath: &str, newpath: &str) -> Result<()> { - let conn = self.pool.get_connection().await?; - let oldpath = self.normalize_path(oldpath); - let newpath = self.normalize_path(newpath); - let components = self.split_path(&newpath); - - if components.is_empty() { - return Err(FsError::RootOperation.into()); - } - - // Resolve old path to get its inode - let ino = self - .resolve_path_with_conn(&conn, &oldpath) - .await? - .ok_or(FsError::NotFound)?; - - // Check if source is a directory (hard links to directories are not allowed) - let mut rows = conn - .query("SELECT mode FROM fs_inode WHERE ino = ?", (ino,)) - .await?; - - if let Some(row) = rows.next().await? { - let mode = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32; - - if (mode & S_IFMT) == super::S_IFDIR { - return Err(FsError::IsADirectory.into()); - } - } else { - return Err(FsError::NotFound.into()); - } - - // Get parent directory of new path - let parent_path = if components.len() == 1 { - "/".to_string() - } else { - format!("/{}", components[..components.len() - 1].join("/")) - }; - - let parent_ino = self - .resolve_path_with_conn(&conn, &parent_path) - .await? - .ok_or(FsError::NotFound)?; - - let name = components.last().unwrap(); - - // Check if new path already exists (single query using parent_ino we already have) - if self.lookup_child(&conn, parent_ino, name).await?.is_some() { - return Err(FsError::AlreadyExists.into()); - } - - // Create directory entry pointing to the same inode - conn.execute( - "INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)", - (name.as_str(), parent_ino, ino), - ) - .await?; - - // Increment link count - conn.execute( - "UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?", - (ino,), - ) - .await?; - - // Populate dentry cache - self.cache_dentry(parent_ino, name, ino); - self.invalidate_parent_attr(parent_ino); - self.invalidate_attr(ino); - - Ok(()) - } - - /// Read the target of a symbolic link - pub async fn readlink(&self, path: &str) -> Result> { - let conn = self.pool.get_connection().await?; - self.readlink_with_conn(&conn, path).await - } - - /// Read the target of a symbolic link using a provided connection - async fn readlink_with_conn(&self, conn: &Connection, path: &str) -> Result> { - let path = self.normalize_path(path); - - let ino = match self.resolve_path_with_conn(conn, &path).await? { - Some(ino) => ino, - None => return Ok(None), - }; - - // Check if it's a symlink by querying the inode - let mut rows = conn - .query("SELECT mode FROM fs_inode WHERE ino = ?", (ino,)) - .await?; - - if let Some(row) = rows.next().await? { - let mode = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32; - - // Check if it's a symlink - if (mode & S_IFMT) != S_IFLNK { - return Err(FsError::NotASymlink.into()); - } - } else { - return Ok(None); - } - - // Read target from fs_symlink table - let mut rows = conn - .query("SELECT target FROM fs_symlink WHERE ino = ?", (ino,)) - .await?; - - if let Some(row) = rows.next().await? { - let target = row - .get_value(0) - .ok() - .and_then(|v| match v { - Value::Text(s) => Some(s.to_string()), - _ => None, - }) - .ok_or(FsError::InvalidPath)?; - Ok(Some(target)) - } else { - Ok(None) - } - } - - /// Remove a file or empty directory - pub async fn remove(&self, path: &str) -> Result<()> { - let conn = self.pool.get_connection().await?; - let path = self.normalize_path(path); - let components = self.split_path(&path); - - if components.is_empty() { - return Err(FsError::RootOperation.into()); - } - - let ino = self - .resolve_path_with_conn(&conn, &path) - .await? - .ok_or(FsError::NotFound)?; - - if ino == ROOT_INO { - return Err(FsError::RootOperation.into()); - } - - // Get stats to check if it's a directory - let stats = self - .stat_with_conn(&conn, &path) - .await? - .ok_or(FsError::NotFound)?; - - // Check if directory is empty - let mut stmt = conn - .prepare_cached("SELECT COUNT(*) FROM fs_dentry WHERE parent_ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if let Some(row) = rows.next().await? { - let count = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - if count > 0 { - return Err(FsError::NotEmpty.into()); - } - } - - // Get parent directory and name - let parent_path = if components.len() == 1 { - "/".to_string() - } else { - format!("/{}", components[..components.len() - 1].join("/")) - }; - - let parent_ino = self - .resolve_path_with_conn(&conn, &parent_path) - .await? - .ok_or(FsError::NotFound)?; - - let name = components.last().unwrap(); - - // Delete the specific directory entry (not all entries pointing to this inode) - let mut stmt = conn - .prepare_cached("DELETE FROM fs_dentry WHERE parent_ino = ? AND name = ?") - .await?; - stmt.execute((parent_ino, name.as_str())).await?; - - // Invalidate cache for this entry - self.invalidate_dentry(parent_ino, name); - self.invalidate_parent_attr(parent_ino); - self.invalidate_attr(ino); - - // Decrement link count - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - - // If removing a directory, decrement parent nlink (removed dir's ".." link) - if stats.is_directory() { - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - let mut stmt = conn - .prepare_cached( - "UPDATE fs_inode SET nlink = nlink - 1, ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", - ) - .await?; - stmt.execute((now_secs, now_secs, now_nsec, now_nsec, parent_ino)) - .await?; - } - - // Check if this was the last link to the inode. POSIX: while open - // handles exist the nlink=0 rows stay alive (readable and writable); - // the last handle drop queues the orphan for process_deferred_reaps. - let link_count = self.get_link_count(&conn, ino).await?; - if link_count == 0 && !self.open_inodes.defer_reap_if_open(ino) { - // Tier Four: drop any pending batched writes — see the matching - // hook in the trait-method `unlink` and in `rename` overwrite. - if let Some(batcher) = &self.write_batcher { - batcher.discard_pending(ino); - } - // Manually handle cascading deletes since we don't use foreign keys - // Delete data blocks - let mut stmt = conn - .prepare_cached("DELETE FROM fs_data WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - - // Delete symlink if exists - let mut stmt = conn - .prepare_cached("DELETE FROM fs_symlink WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - - // Delete inode - let mut stmt = conn - .prepare_cached("DELETE FROM fs_inode WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - } - - self.invalidate_dentry(parent_ino, name); - self.invalidate_parent_attr(parent_ino); - self.invalidate_attr(ino); - self.cache_negative_dentry(parent_ino, name); - Ok(()) - } - - /// Change file ownership - /// - /// Changes the user and/or group ownership of a file. - /// Pass None for uid or gid to leave that value unchanged. - pub async fn chown(&self, ino: i64, uid: Option, gid: Option) -> Result<()> { - if uid.is_none() && gid.is_none() { - return Ok(()); - } - - let conn = self.pool.get_connection().await?; - - // Build the update query dynamically based on which values are provided - let mut updates = Vec::new(); - let mut values: Vec = Vec::new(); - - if let Some(uid) = uid { - updates.push("uid = ?"); - values.push(Value::Integer(uid as i64)); - } - if let Some(gid) = gid { - updates.push("gid = ?"); - values.push(Value::Integer(gid as i64)); - } - - values.push(Value::Integer(ino)); - let sql = format!("UPDATE fs_inode SET {} WHERE ino = ?", updates.join(", ")); - conn.execute(&sql, values).await?; - self.invalidate_attr(ino); - - Ok(()) - } - - /// Rename/move a file or directory. - /// - /// This operation is atomic - either all changes succeed or none do. - pub async fn rename(&self, from: &str, to: &str) -> Result<()> { - let conn = self.pool.get_connection().await?; - let from_path = self.normalize_path(from); - let to_path = self.normalize_path(to); - - // Cannot rename root - if from_path == "/" { - return Err(FsError::RootOperation.into()); - } - - // Get source inode - let src_ino = self - .resolve_path_with_conn(&conn, &from_path) - .await? - .ok_or(FsError::NotFound)?; - - // Get source stats to check if it's a directory - let src_stats = self - .stat_with_conn(&conn, &from_path) - .await? - .ok_or(FsError::NotFound)?; - - // Prevent renaming a directory into its own subtree (would create a cycle) - if src_stats.is_directory() { - let from_prefix = format!("{}/", from_path); - if to_path.starts_with(&from_prefix) || to_path == from_path { - return Err(FsError::InvalidRename.into()); - } - } - - // Parse source path to get parent and name - let from_components = self.split_path(&from_path); - let src_name = from_components.last().ok_or(FsError::InvalidPath)?; - let src_parent_path = if from_components.len() == 1 { - "/".to_string() - } else { - format!( - "/{}", - from_components[..from_components.len() - 1].join("/") - ) - }; - let src_parent_ino = self - .resolve_path_with_conn(&conn, &src_parent_path) - .await? - .ok_or(FsError::NotFound)?; - - // Parse destination path to get parent and name - let to_components = self.split_path(&to_path); - if to_components.is_empty() { - return Err(FsError::RootOperation.into()); - } - let dst_name = to_components.last().unwrap(); - let dst_parent_path = if to_components.len() == 1 { - "/".to_string() - } else { - format!("/{}", to_components[..to_components.len() - 1].join("/")) - }; - let dst_parent_ino = self - .resolve_path_with_conn(&conn, &dst_parent_path) - .await? - .ok_or(FsError::NotFound)?; - - // Clone strings for use inside the transaction closure - let src_name = src_name.clone(); - let dst_name = dst_name.clone(); - - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - - let result: Result> = async { - let mut replaced_dst_ino = None; - // Check if destination exists (inside transaction for atomicity) - if let Some(dst_ino) = self.resolve_path_with_conn(&conn, &to_path).await? { - replaced_dst_ino = Some(dst_ino); - let dst_stats = self.stat_with_conn(&conn, &to_path).await?.ok_or(FsError::NotFound)?; - - // Can't replace directory with non-directory - if dst_stats.is_directory() && !src_stats.is_directory() { - return Err(FsError::IsADirectory.into()); - } - - // Can't replace non-directory with directory - if !dst_stats.is_directory() && src_stats.is_directory() { - return Err(FsError::NotADirectory.into()); - } - - // If destination is directory, it must be empty - if dst_stats.is_directory() { - let mut stmt = conn - .prepare_cached("SELECT COUNT(*) FROM fs_dentry WHERE parent_ino = ?") - .await?; - let mut rows = stmt.query((dst_ino,)).await?; - - if let Some(row) = rows.next().await? { - let count = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - if count > 0 { - return Err(FsError::NotEmpty.into()); - } - } - } - - // Remove destination entry - let mut stmt = conn - .prepare_cached("DELETE FROM fs_dentry WHERE parent_ino = ? AND name = ?") - .await?; - stmt.execute((dst_parent_ino, dst_name.as_str())).await?; - - // Decrement link count - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?") - .await?; - stmt.execute((dst_ino,)).await?; - - // Clean up destination inode if no more links (deferred while - // open handles exist — see OpenInodes) - let link_count = self.get_link_count(&conn, dst_ino).await?; - if link_count == 0 && !self.open_inodes.defer_reap_if_open(dst_ino) { - // Tier Four: drop pending batched writes for the - // soon-to-be-deleted inode. Without this, a later - // drain (Explicit drains run drain_pending_batched - // which touches every pending inode in one txn) tries - // to write into a missing fs_inode row and fails the - // whole batch with NotFound. - if let Some(batcher) = &self.write_batcher { - batcher.discard_pending(dst_ino); - } - let mut stmt = conn - .prepare_cached("DELETE FROM fs_data WHERE ino = ?") - .await?; - stmt.execute((dst_ino,)).await?; - let mut stmt = conn - .prepare_cached("DELETE FROM fs_symlink WHERE ino = ?") - .await?; - stmt.execute((dst_ino,)).await?; - let mut stmt = conn - .prepare_cached("DELETE FROM fs_inode WHERE ino = ?") - .await?; - stmt.execute((dst_ino,)).await?; - } - } - - // Update the dentry: change parent and/or name - let mut stmt = conn - .prepare_cached( - "UPDATE fs_dentry SET parent_ino = ?, name = ? WHERE parent_ino = ? AND name = ?", - ) - .await?; - stmt.execute(( - dst_parent_ino, - dst_name.as_str(), - src_parent_ino, - src_name.as_str(), - )) - .await?; - - // If renaming a directory across parents, adjust parent nlink counts - if src_stats.is_directory() && src_parent_ino != dst_parent_ino { - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?") - .await?; - stmt.execute((src_parent_ino,)).await?; - - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?") - .await?; - stmt.execute((dst_parent_ino,)).await?; - } - - // Update ctime of the inode - let dur = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default(); - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET ctime = ?, ctime_nsec = ? WHERE ino = ?") - .await?; - stmt.execute((now_secs, now_nsec, src_ino)).await?; - - // Update source parent directory timestamps - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?") - .await?; - stmt.execute((now_secs, now_secs, now_nsec, now_nsec, src_parent_ino)).await?; - - // Update destination parent directory timestamps - if dst_parent_ino != src_parent_ino { - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?") - .await?; - stmt.execute((now_secs, now_secs, now_nsec, now_nsec, dst_parent_ino)).await?; - } - - Ok(replaced_dst_ino) - } - .await; - - match result { - Ok(replaced_dst_ino) => { - txn.commit().await?; - - // Invalidate cache for source and destination - self.invalidate_dentry(src_parent_ino, &src_name); - self.invalidate_dentry(dst_parent_ino, &dst_name); - self.invalidate_attr(src_ino); - self.invalidate_parent_attr(src_parent_ino); - self.invalidate_parent_attr(dst_parent_ino); - if let Some(dst_ino) = replaced_dst_ino { - self.invalidate_attr(dst_ino); - } - - // Add exact post-rename namespace state to the caches. - if src_parent_ino != dst_parent_ino || src_name != dst_name { - self.cache_negative_dentry(src_parent_ino, &src_name); - } - self.cache_dentry(dst_parent_ino, &dst_name, src_ino); - - Ok(()) - } - Err(e) => { - let _ = txn.rollback().await; - Err(e) - } - } - } - - /// Get filesystem statistics - /// - /// Returns the total number of inodes and bytes used by file contents. - pub async fn statfs(&self) -> Result { - self.drain_all().await?; - let conn = self.pool.get_connection().await?; - // Count total inodes - let mut stmt = conn.prepare_cached("SELECT COUNT(*) FROM fs_inode").await?; - let mut rows = stmt.query(()).await?; - - let inodes = if let Some(row) = rows.next().await? { - row.get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u64 - } else { - 0 - }; - - // Sum total bytes used (from file sizes in inodes) - let mut stmt = conn - .prepare_cached("SELECT COALESCE(SUM(size), 0) FROM fs_inode") - .await?; - let mut rows = stmt.query(()).await?; - - let bytes_used = if let Some(row) = rows.next().await? { - row.get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u64 - } else { - 0 - }; - - Ok(FilesystemStats { inodes, bytes_used }) - } - - /// Synchronize file data to persistent storage - /// - /// Temporarily enables FULL synchronous mode, runs a transaction to force - /// a checkpoint, then restores NORMAL mode. This ensures durability while - /// maintaining high performance for normal operations. - /// - /// Note: The path parameter is ignored since all data is in a single database. - pub async fn fsync(&self, _path: &str) -> Result<()> { - self.drain_all().await?; - let conn = self.pool.get_connection().await?; - conn.prepare_cached(DURABLE_SYNCHRONOUS_SQL) - .await? - .execute(()) - .await?; - checkpoint_wal(&conn).await?; - conn.prepare_cached(BASELINE_SYNCHRONOUS_SQL) - .await? - .execute(()) - .await?; - Ok(()) - } - - /// Open a file and return a file handle. - /// - /// The returned handle can be used for efficient read/write/fsync operations - /// without requiring path lookups on each operation. - pub async fn open(&self, path: &str) -> Result { - let path = self.normalize_path(path); - let ino = self.resolve_path(&path).await?.ok_or(FsError::NotFound)?; - - Ok(Arc::new(AgentFSFile { - pool: self.pool.clone(), - ino, - chunk_size: self.chunk_size, - inline_threshold: self.inline_threshold, - attr_cache: self.attr_cache.clone(), - write_batcher: self.write_batcher.clone(), - overlay_reads: self.overlay_reads, - _open_guard: Some(self.open_inodes.guard(ino)), - })) - } - - /// Get the number of chunks for a given inode (for testing). - /// Drains any pending batched writes first so the returned count reflects - /// the full committed state — Tier 4 deferred SQLite commits until fsync - /// or timer, so tests that inspect `fs_data` directly need a sync point. - #[cfg(test)] - async fn get_chunk_count(&self, ino: i64) -> Result { - self.drain_inode_writes(ino).await?; - let conn = self.pool.get_connection().await?; - let mut rows = conn - .query("SELECT COUNT(*) FROM fs_data WHERE ino = ?", (ino,)) - .await?; - - if let Some(row) = rows.next().await? { - Ok(row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0)) - } else { - Ok(0) - } - } - - #[cfg(test)] - async fn get_storage_state(&self, ino: i64) -> Result<(i64, Option>)> { - self.drain_inode_writes(ino).await?; - let conn = self.pool.get_connection().await?; - let mut rows = conn - .query( - "SELECT storage_kind, data_inline FROM fs_inode WHERE ino = ?", - (ino,), - ) - .await?; - - if let Some(row) = rows.next().await? { - let storage_kind = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(STORAGE_CHUNKED); - let data_inline = match row.get_value(1) { - Ok(Value::Blob(data)) => Some(data), - _ => None, - }; - Ok((storage_kind, data_inline)) - } else { - Err(FsError::NotFound.into()) - } - } -} - -#[async_trait] -impl FileSystem for AgentFS { - async fn lookup(&self, parent_ino: i64, name: &str) -> Result> { - crate::profiling::record_lookup(); - if name.len() > MAX_NAME_LEN { - return Err(FsError::NameTooLong.into()); - } - - // Connection-free fast paths via the in-memory caches. These are the - // same caches (and invalidation semantics) that `lookup_child` already - // trusts; consulting them BEFORE acquiring a pool connection avoids a - // wasted acquire/release on every cache hit. This is the clone hot - // path: `OverlayFS::resolve_delta_parent` does O(depth) negative - // delta-parent probes per base-layer lookup, all of which are negative - // cache hits that previously each took a connection. - if name != ".." { - if self.negative_dentry_cache.contains(parent_ino, name) { - crate::profiling::record_negative_lookup(); - return Ok(None); - } - if let Some(child_ino) = self.dentry_cache.get(parent_ino, name) { - if let Some(mut stats) = self.attr_cache.get(child_ino) { - self.merge_pending_view(child_ino, Some(&mut stats)); - return Ok(Some(stats)); - } - } - } - - let conn = self.pool.get_connection().await?; - - // Handle ".." by finding the parent of parent_ino - if name == ".." { - if parent_ino == ROOT_INO { - // Root's parent is itself - return self.getattr_with_conn(&conn, ROOT_INO).await; - } - let mut stmt = conn - .prepare_cached("SELECT parent_ino FROM fs_dentry WHERE ino = ? LIMIT 1") - .await?; - let mut rows = stmt.query((parent_ino,)).await?; - let parent = if let Some(row) = rows.next().await? { - row.get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(ROOT_INO) - } else { - ROOT_INO - }; - return self.getattr_with_conn(&conn, parent).await; - } - - // Look up the child inode - let child_ino = match self.lookup_child(&conn, parent_ino, name).await? { - Some(ino) => ino, - None => { - crate::profiling::record_negative_lookup(); - return Ok(None); - } - }; - // Tier Four: do NOT call `drain_inode_writes` here. The single- - // connection ephemeral pool (and even the file-backed pool under - // contention) would deadlock — we already hold the only connection - // permit, and `drain_inode_writes` -> `drain_pending_batched` tries - // to acquire one. Read SQLite, then merge the batcher's pending - // max-end into the size field the same way `getattr` does. - - // Get stats for the child inode - let mut stmt = conn - .prepare_cached("SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime, rdev, atime_nsec, mtime_nsec, ctime_nsec FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((child_ino,)).await?; - - if let Some(row) = rows.next().await? { - let mut stats = Self::build_stats_from_row(&row)?; - self.merge_pending_view(child_ino, Some(&mut stats)); - // Cache the lookup result - self.cache_dentry(parent_ino, name, child_ino); - self.cache_attr(stats.clone()); - Ok(Some(stats)) - } else { - Ok(None) - } - } - - async fn getattr(&self, ino: i64) -> Result> { - crate::profiling::record_getattr(); - // Connection-free fast path: an attr-cache hit needs no pool connection. - // The cache is invalidated on every write (enqueue removes the entry), - // so a hit means there is no uncommitted pending write to merge; the - // merge below is therefore an idempotent no-op but is kept for safety. - // Same cache `getattr_with_conn` already trusts, consulted before the - // acquire. - if let Some(mut stats) = self.attr_cache.get(ino) { - self.merge_pending_view(ino, Some(&mut stats)); - return Ok(Some(stats)); - } - // Tier Four: don't drain — read SQLite metadata and OR in the - // batcher's peek_pending_max_end so the size view reflects pending - // writes that haven't been committed yet. Refresh the attr cache - // with the merged size so subsequent direct cache reads agree with - // what we just returned. - let conn = self.pool.get_connection().await?; - let mut stats = self.getattr_with_conn(&conn, ino).await?; - if let Some(s) = stats.as_mut() { - let pre = s.size; - self.merge_pending_view(ino, Some(s)); - if s.size != pre { - self.cache_attr(s.clone()); - } - } - Ok(stats) - } - - /// DB-backed regular files qualify for `FOPEN_KEEP_CACHE`: every mutation - /// path through a mount is kernel-originated (the kernel's pages stay - /// coherent for its own writes) and the adapter's fingerprint guard - /// revalidates mtime/ctime/size at each open, so out-of-band SDK writers - /// are caught exactly like external edits to host-backed base files. - /// Kill switch: `AGENTFS_KEEPCACHE_DELTA=0` restores the old policy - /// where only host-backed base-layer files were eligible. - async fn keep_cache_for_read_open(&self, ino: i64, flags: i32) -> Result> { - if (flags & libc::O_ACCMODE) != libc::O_RDONLY || (flags & libc::O_TRUNC) != 0 { - return Ok(None); - } - if !keepcache_delta_enabled() { - return Ok(None); - } - let Some(stats) = FileSystem::getattr(self, ino).await? else { - return Ok(None); - }; - Ok(stats.is_file().then_some(stats)) - } - - async fn readlink(&self, ino: i64) -> Result> { - let conn = self.pool.get_connection().await?; - - // Check if the inode exists and is a symlink - let mut stmt = conn - .prepare_cached("SELECT mode FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if let Some(row) = rows.next().await? { - let mode = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32; - - if (mode & S_IFMT) != S_IFLNK { - return Err(FsError::NotASymlink.into()); - } - } else { - return Ok(None); - } - - // Read target from fs_symlink table - let mut stmt = conn - .prepare_cached("SELECT target FROM fs_symlink WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if let Some(row) = rows.next().await? { - let target = row - .get_value(0) - .ok() - .and_then(|v| match v { - Value::Text(s) => Some(s.to_string()), - _ => None, - }) - .ok_or(FsError::InvalidPath)?; - Ok(Some(target)) - } else { - Ok(None) - } - } - - async fn readdir(&self, ino: i64) -> Result>> { - crate::profiling::record_readdir(); - let conn = self.pool.get_connection().await?; - - // Check if inode exists and is a directory - let mut stmt = conn - .prepare_cached("SELECT mode FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if let Some(row) = rows.next().await? { - let mode = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32; - - if (mode & S_IFMT) != super::S_IFDIR { - return Err(FsError::NotADirectory.into()); - } - } else { - return Ok(None); - } - - let mut stmt = conn - .prepare_cached("SELECT name FROM fs_dentry WHERE parent_ino = ? ORDER BY name") - .await?; - let mut rows = stmt.query((ino,)).await?; - - let mut entries = Vec::new(); - while let Some(row) = rows.next().await? { - let name = row - .get_value(0) - .ok() - .and_then(|v| { - if let Value::Text(s) = v { - Some(s.clone()) - } else { - None - } - }) - .unwrap_or_default(); - if !name.is_empty() { - entries.push(name); - } - } - - Ok(Some(entries)) - } - - async fn readdir_plus(&self, ino: i64) -> Result>> { - crate::profiling::record_readdir_plus(); - self.drain_all().await?; - let conn = self.pool.get_connection().await?; - - // Check if inode exists and is a directory - let mut stmt = conn - .prepare_cached("SELECT mode FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if let Some(row) = rows.next().await? { - let mode = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32; - - if (mode & S_IFMT) != super::S_IFDIR { - return Err(FsError::NotADirectory.into()); - } - } else { - return Ok(None); - } - - let mut stmt = conn.prepare_cached("SELECT d.name, i.ino, i.mode, i.nlink, i.uid, i.gid, i.size, i.atime, i.mtime, i.ctime, i.rdev, i.atime_nsec, i.mtime_nsec, i.ctime_nsec - FROM fs_dentry d - JOIN fs_inode i ON d.ino = i.ino - WHERE d.parent_ino = ? - ORDER BY d.name" - ).await?; - let mut rows = stmt.query((ino,)).await?; - - let mut entries = Vec::new(); - while let Some(row) = rows.next().await? { - let name = row - .get_value(0) - .ok() - .and_then(|v| { - if let Value::Text(s) = v { - Some(s.clone()) - } else { - None - } - }) - .unwrap_or_default(); - - if name.is_empty() { - continue; - } - - let entry_ino = row - .get_value(1) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - - let stats = Stats { - ino: entry_ino, - mode: row - .get_value(2) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - nlink: row - .get_value(3) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(1) as u32, - uid: row - .get_value(4) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - gid: row - .get_value(5) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - size: row - .get_value(6) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - atime: row - .get_value(7) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - mtime: row - .get_value(8) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - ctime: row - .get_value(9) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0), - atime_nsec: row - .get_value(11) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - mtime_nsec: row - .get_value(12) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - ctime_nsec: row - .get_value(13) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32, - rdev: row - .get_value(10) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u64, - }; - - self.cache_attr(stats.clone()); - entries.push(DirEntry { name, stats }); - } - - Ok(Some(entries)) - } - - async fn chmod(&self, ino: i64, mode: u32) -> Result<()> { - self.prepare_attr_change(ino).await?; - let conn = self.pool.get_connection().await?; - // BEGIN IMMEDIATE so this serialises with concurrent batcher drain - // transactions instead of racing them as an autocommit statement - // (turso reports such write/write races as "database snapshot is - // stale" instead of waiting on the write lock). - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let result: Result<()> = async { - // Get current mode to preserve file type bits - let mut stmt = conn - .prepare_cached("SELECT mode FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - let current_mode = if let Some(row) = rows.next().await? { - row.get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32 - } else { - return Err(FsError::NotFound.into()); - }; - - // Preserve file type bits (upper bits), replace permission bits (lower 12 bits) - let new_mode = (current_mode & S_IFMT) | (mode & 0o7777); - - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - let mut stmt = conn - .prepare_cached( - "UPDATE fs_inode SET mode = ?, ctime = ?, ctime_nsec = ? WHERE ino = ?", - ) - .await?; - stmt.execute((new_mode as i64, now_secs, now_nsec, ino)) - .await?; - Ok(()) - } - .await; - - match result { - Ok(()) => { - txn.commit().await?; - self.invalidate_attr(ino); - Ok(()) - } - Err(error) => { - let _ = txn.rollback().await; - Err(error) - } - } - } - - async fn chown(&self, ino: i64, uid: Option, gid: Option) -> Result<()> { - if uid.is_none() && gid.is_none() { - return Ok(()); - } - self.prepare_attr_change(ino).await?; - - let conn = self.pool.get_connection().await?; - // BEGIN IMMEDIATE: see `chmod` — avoid autocommit write/write races - // with concurrent batcher drain transactions. - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let result: Result<()> = async { - // Verify inode exists - let mut stmt = conn - .prepare_cached("SELECT ino FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if rows.next().await?.is_none() { - return Err(FsError::NotFound.into()); - } - - // Build the update query dynamically based on which values are provided - let mut updates = Vec::new(); - let mut values: Vec = Vec::new(); - - if let Some(uid) = uid { - updates.push("uid = ?"); - values.push(Value::Integer(uid as i64)); - } - if let Some(gid) = gid { - updates.push("gid = ?"); - values.push(Value::Integer(gid as i64)); - } - - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - updates.push("ctime = ?"); - values.push(Value::Integer(now_secs)); - updates.push("ctime_nsec = ?"); - values.push(Value::Integer(now_nsec)); - - values.push(Value::Integer(ino)); - let sql = format!("UPDATE fs_inode SET {} WHERE ino = ?", updates.join(", ")); - conn.execute(&sql, values).await?; - Ok(()) - } - .await; - - match result { - Ok(()) => { - txn.commit().await?; - self.invalidate_attr(ino); - Ok(()) - } - Err(error) => { - let _ = txn.rollback().await; - Err(error) - } - } - } - - async fn utimens(&self, ino: i64, atime: TimeChange, mtime: TimeChange) -> Result<()> { - if matches!(atime, TimeChange::Omit) && matches!(mtime, TimeChange::Omit) { - return Ok(()); - } - - // Group-commit fast path: with FUSE writeback caching the kernel sends - // one SETATTR (mtime) per freshly written file, usually while that - // file's data is pending in the write batcher (and sometimes after it - // already drained). Instead of paying a dedicated SQLite transaction - // per file for the time UPDATE, stash the resolved values in the - // inode's pending entry (created on demand) — the batcher commits them - // inside its next drain transaction (`apply_pending_times_with_conn`), - // and `merge_pending_view` overlays them onto getattr/lookup results so - // the change is visible immediately. Falls through to the direct - // (transaction-wrapped) UPDATE when overlay reads are disabled or the - // legacy drain is requested. - if !drain_on_setattr() && self.overlay_reads { - if let Some(batcher) = &self.write_batcher { - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now = (dur.as_secs() as i64, dur.subsec_nanos() as i64); - let resolve = |tc: TimeChange| -> Option<(i64, i64)> { - match tc { - TimeChange::Set(secs, nsec) => Some((secs, nsec as i64)), - TimeChange::Now => Some(now), - TimeChange::Omit => None, - } - }; - let change = PendingTimeChange { - atime: resolve(atime), - mtime: resolve(mtime), - // utimens always bumps ctime. - ctime: Some(now), - }; - batcher.stash_pending_times(ino, change); - self.invalidate_attr(ino); - return Ok(()); - } - } - - self.prepare_attr_change(ino).await?; - let conn = self.pool.get_connection().await?; - // BEGIN IMMEDIATE: see `chmod` — avoid autocommit write/write races - // with concurrent batcher drain transactions. - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let result: Result<()> = async { - // Verify inode exists - let mut stmt = conn - .prepare_cached("SELECT ino FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - if rows.next().await?.is_none() { - return Err(FsError::NotFound.into()); - } - - let mut updates = Vec::new(); - let mut values: Vec = Vec::new(); - - let resolve = |tc: TimeChange| -> (i64, i64) { - match tc { - TimeChange::Set(secs, nsec) => (secs, nsec as i64), - TimeChange::Now => { - let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - (dur.as_secs() as i64, dur.subsec_nanos() as i64) - } - TimeChange::Omit => unreachable!(), - } - }; - - if !matches!(atime, TimeChange::Omit) { - let (secs, nsec) = resolve(atime); - updates.push("atime = ?"); - values.push(Value::Integer(secs)); - updates.push("atime_nsec = ?"); - values.push(Value::Integer(nsec)); - } - - if !matches!(mtime, TimeChange::Omit) { - let (secs, nsec) = resolve(mtime); - updates.push("mtime = ?"); - values.push(Value::Integer(secs)); - updates.push("mtime_nsec = ?"); - values.push(Value::Integer(nsec)); - } - - // Also update ctime - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - updates.push("ctime = ?"); - values.push(Value::Integer(dur.as_secs() as i64)); - updates.push("ctime_nsec = ?"); - values.push(Value::Integer(dur.subsec_nanos() as i64)); - - values.push(Value::Integer(ino)); - let sql = format!("UPDATE fs_inode SET {} WHERE ino = ?", updates.join(", ")); - conn.execute(&sql, values).await?; - Ok(()) - } - .await; - - match result { - Ok(()) => { - txn.commit().await?; - self.invalidate_attr(ino); - Ok(()) - } - Err(error) => { - let _ = txn.rollback().await; - Err(error) - } - } - } - - async fn open(&self, ino: i64, _flags: i32) -> Result { - let conn = self.pool.get_connection().await?; - - // Verify inode exists - let mut stmt = conn - .prepare_cached("SELECT ino FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if rows.next().await?.is_none() { - return Err(FsError::NotFound.into()); - } - - Ok(Arc::new(AgentFSFile { - pool: self.pool.clone(), - ino, - chunk_size: self.chunk_size, - inline_threshold: self.inline_threshold, - attr_cache: self.attr_cache.clone(), - write_batcher: self.write_batcher.clone(), - overlay_reads: self.overlay_reads, - _open_guard: Some(self.open_inodes.guard(ino)), - })) - } - - async fn mkdir( - &self, - parent_ino: i64, - name: &str, - mode: u32, - uid: u32, - gid: u32, - ) -> Result { - if name.len() > MAX_NAME_LEN { - return Err(FsError::NameTooLong.into()); - } - let conn = self.pool.get_connection().await?; - // BEGIN IMMEDIATE: see `chmod` — multi-statement metadata mutations - // must not run as autocommit statements that race the write batcher's - // drain transactions (turso reports such write/write races as - // "database snapshot is stale" instead of waiting on the write lock). - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let result: Result = async { - // Check if already exists - if self.lookup_child(&conn, parent_ino, name).await?.is_some() { - return Err(FsError::AlreadyExists.into()); - } - - // Create inode - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - let mut stmt = conn - .prepare_cached( - "INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec) - VALUES (?, ?, ?, 0, ?, ?, ?, ?, ?, ?) RETURNING ino", - ) - .await?; - let dir_mode = super::S_IFDIR | (mode & 0o7777); - let row = stmt - .query_row(( - dir_mode as i64, - uid, - gid, - now_secs, - now_secs, - now_secs, - now_nsec, - now_nsec, - now_nsec, - )) - .await?; - - let ino = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; - - // Create directory entry - let mut stmt = conn - .prepare_cached("INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)") - .await?; - stmt.execute((name, parent_ino, ino)).await?; - - // Set nlink to 2 for new directory (self "." + parent's dentry) - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET nlink = 2 WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - - // Increment parent nlink (new directory's ".." link) and update timestamps - let mut stmt = conn - .prepare_cached( - "UPDATE fs_inode SET nlink = nlink + 1, ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", - ) - .await?; - stmt.execute((now_secs, now_secs, now_nsec, now_nsec, parent_ino)) - .await?; - - Ok(Stats { - ino, - mode: dir_mode, - nlink: 2, - uid, - gid, - size: 0, - atime: now_secs, - mtime: now_secs, - ctime: now_secs, - atime_nsec: now_nsec as u32, - mtime_nsec: now_nsec as u32, - ctime_nsec: now_nsec as u32, - rdev: 0, - }) - } - .await; - - match result { - Ok(stats) => { - txn.commit().await?; - // Populate dentry cache only after the transaction is durable. - self.cache_dentry(parent_ino, name, stats.ino); - self.invalidate_parent_attr(parent_ino); - self.cache_attr(stats.clone()); - Ok(stats) - } - Err(error) => { - let _ = txn.rollback().await; - Err(error) - } - } - } - - async fn create_file( - &self, - parent_ino: i64, - name: &str, - mode: u32, - uid: u32, - gid: u32, - ) -> Result<(Stats, BoxedFile)> { - if name.len() > MAX_NAME_LEN { - return Err(FsError::NameTooLong.into()); - } - let conn = self.pool.get_connection().await?; - - // No existence pre-check: fs_dentry's UNIQUE(parent_ino, name) makes - // the dentry INSERT below the authoritative collision detector (its - // Constraint error maps to AlreadyExists and the transaction drop - // rolls back the inode row). Saves one SELECT on the synchronous - // create path that every git-clone file pays. - - // Prepare statements before starting the transaction - let mut inode_stmt = conn - .prepare_cached( - "INSERT INTO fs_inode (mode, nlink, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec, data_inline, storage_kind) - VALUES (?, 1, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING ino", - ) - .await?; - let mut dentry_stmt = conn - .prepare_cached("INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)") - .await?; - - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - let file_mode = S_IFREG | (mode & 0o7777); - - let row = inode_stmt - .query_row(( - file_mode as i64, - uid, - gid, - now_secs, - now_secs, - now_secs, - now_nsec, - now_nsec, - now_nsec, - Value::Blob(Vec::new()), - STORAGE_INLINE, - )) - .await?; - - let ino = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; - - match dentry_stmt.execute((name, parent_ino, ino)).await { - Ok(_) => {} - Err(turso::Error::Constraint(_)) => return Err(FsError::AlreadyExists.into()), - Err(error) => return Err(error.into()), - } - - // Parent mtime/ctime: stash into the batcher overlay (committed by the - // next group drain, served immediately via merge_pending_view) instead - // of paying an UPDATE on the synchronous create path. Falls back to - // the in-transaction UPDATE when the overlay cannot serve reads. - let stash_parent_times = self.overlay_reads && self.write_batcher.is_some(); - if !stash_parent_times { - conn.execute( - "UPDATE fs_inode SET ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", - (now_secs, now_secs, now_nsec, now_nsec, parent_ino), - ) - .await?; - } - - txn.commit().await?; - - if stash_parent_times { - if let Some(batcher) = &self.write_batcher { - batcher.stash_pending_times( - parent_ino, - PendingTimeChange { - atime: None, - mtime: Some((now_secs, now_nsec)), - ctime: Some((now_secs, now_nsec)), - }, - ); - } - } - - self.cache_dentry(parent_ino, name, ino); - self.invalidate_parent_attr(parent_ino); - - let stats = Stats { - ino, - mode: file_mode, - nlink: 1, - uid, - gid, - size: 0, - atime: now_secs, - mtime: now_secs, - ctime: now_secs, - atime_nsec: now_nsec as u32, - mtime_nsec: now_nsec as u32, - ctime_nsec: now_nsec as u32, - rdev: 0, - }; - self.cache_attr(stats.clone()); - - let file: BoxedFile = Arc::new(AgentFSFile { - pool: self.pool.clone(), - ino, - chunk_size: self.chunk_size, - inline_threshold: self.inline_threshold, - attr_cache: self.attr_cache.clone(), - write_batcher: self.write_batcher.clone(), - overlay_reads: self.overlay_reads, - _open_guard: Some(self.open_inodes.guard(ino)), - }); - - Ok((stats, file)) - } - - async fn mknod( - &self, - parent_ino: i64, - name: &str, - mode: u32, - rdev: u64, - uid: u32, - gid: u32, - ) -> Result { - if name.len() > MAX_NAME_LEN { - return Err(FsError::NameTooLong.into()); - } - let conn = self.pool.get_connection().await?; - // BEGIN IMMEDIATE: see `mkdir` — never race the batcher's drain - // transactions with autocommit metadata writes. - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let result: Result = async { - // Check if already exists - if self.lookup_child(&conn, parent_ino, name).await?.is_some() { - return Err(FsError::AlreadyExists.into()); - } - - // Create inode with mode and rdev - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - let mut stmt = conn - .prepare_cached( - "INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime, rdev, atime_nsec, mtime_nsec, ctime_nsec) - VALUES (?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?) RETURNING ino", - ) - .await?; - let row = stmt - .query_row(( - mode as i64, - uid, - gid, - now_secs, - now_secs, - now_secs, - rdev as i64, - now_nsec, - now_nsec, - now_nsec, - )) - .await?; - - let ino = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; - - // Create directory entry - let mut stmt = conn - .prepare_cached("INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)") - .await?; - stmt.execute((name, parent_ino, ino)).await?; - - // Increment link count - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - - // Update parent directory ctime and mtime - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?") - .await?; - stmt.execute((now_secs, now_secs, now_nsec, now_nsec, parent_ino)) - .await?; - - Ok(Stats { - ino, - mode, - nlink: 1, - uid, - gid, - size: 0, - atime: now_secs, - mtime: now_secs, - ctime: now_secs, - atime_nsec: now_nsec as u32, - mtime_nsec: now_nsec as u32, - ctime_nsec: now_nsec as u32, - rdev, - }) - } - .await; - - match result { - Ok(stats) => { - txn.commit().await?; - // Populate dentry cache only after the transaction is durable. - self.cache_dentry(parent_ino, name, stats.ino); - self.invalidate_parent_attr(parent_ino); - self.cache_attr(stats.clone()); - Ok(stats) - } - Err(error) => { - let _ = txn.rollback().await; - Err(error) - } - } - } - - async fn symlink( - &self, - parent_ino: i64, - name: &str, - target: &str, - uid: u32, - gid: u32, - ) -> Result { - if name.len() > MAX_NAME_LEN { - return Err(FsError::NameTooLong.into()); - } - let conn = self.pool.get_connection().await?; - // BEGIN IMMEDIATE: see `mkdir` — never race the batcher's drain - // transactions with autocommit metadata writes. - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let result: Result = async { - // Check if entry already exists - if self.lookup_child(&conn, parent_ino, name).await?.is_some() { - return Err(FsError::AlreadyExists.into()); - } - - // Create inode for symlink - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - let mode = S_IFLNK | 0o777; // Symlinks typically have 777 permissions - let size = target.len() as i64; - - let mut stmt = conn - .prepare_cached( - "INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime, atime_nsec, mtime_nsec, ctime_nsec) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING ino", - ) - .await?; - let row = stmt - .query_row(( - mode, uid, gid, size, now_secs, now_secs, now_secs, now_nsec, now_nsec, - now_nsec, - )) - .await?; - - let ino = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .ok_or_else(|| Error::Internal("failed to get inode".to_string()))?; - - // Store symlink target - conn.execute( - "INSERT INTO fs_symlink (ino, target) VALUES (?, ?)", - (ino, target), - ) - .await?; - - // Create directory entry - conn.execute( - "INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)", - (name, parent_ino, ino), - ) - .await?; - - // Increment link count - conn.execute( - "UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?", - (ino,), - ) - .await?; - - // Update parent directory ctime and mtime - conn.execute( - "UPDATE fs_inode SET ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", - (now_secs, now_secs, now_nsec, now_nsec, parent_ino), - ) - .await?; - - Ok(Stats { - ino, - mode, - nlink: 1, - uid, - gid, - size, - atime: now_secs, - mtime: now_secs, - ctime: now_secs, - atime_nsec: now_nsec as u32, - mtime_nsec: now_nsec as u32, - ctime_nsec: now_nsec as u32, - rdev: 0, - }) - } - .await; - - match result { - Ok(stats) => { - txn.commit().await?; - // Populate dentry cache only after the transaction is durable. - self.cache_dentry(parent_ino, name, stats.ino); - self.invalidate_parent_attr(parent_ino); - self.cache_attr(stats.clone()); - Ok(stats) - } - Err(error) => { - let _ = txn.rollback().await; - Err(error) - } - } - } - - async fn unlink(&self, parent_ino: i64, name: &str) -> Result<()> { - if name.len() > MAX_NAME_LEN { - return Err(FsError::NameTooLong.into()); - } - self.process_deferred_reaps().await?; - let conn = self.pool.get_connection().await?; - // BEGIN IMMEDIATE: this is the path that intermittently failed with - // "database snapshot is stale" -> EIO when its autocommit statements - // raced the write batcher's drain transactions (git unlinking - // `.git/config.lock` during a clone). The transaction also makes the - // dentry/nlink/inode removal atomic. - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let result: Result<(i64, bool)> = async { - // Look up the child inode - let ino = self - .lookup_child(&conn, parent_ino, name) - .await? - .ok_or(FsError::NotFound)?; - - // Check if it's a directory (use rmdir for directories) - let mut stmt = conn - .prepare_cached("SELECT mode FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if let Some(row) = rows.next().await? { - let mode = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32; - - if (mode & S_IFMT) == super::S_IFDIR { - return Err(FsError::IsADirectory.into()); - } - } - - // Delete the directory entry - let mut stmt = conn - .prepare_cached("DELETE FROM fs_dentry WHERE parent_ino = ? AND name = ?") - .await?; - stmt.execute((parent_ino, name)).await?; - - // Update parent directory mtime and ctime - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?") - .await?; - stmt.execute((now_secs, now_secs, now_nsec, now_nsec, parent_ino)) - .await?; - - // Decrement link count and update ctime - let mut stmt = conn - .prepare_cached( - "UPDATE fs_inode SET nlink = nlink - 1, ctime = ?, ctime_nsec = ? WHERE ino = ?", - ) - .await?; - stmt.execute((now_secs, now_nsec, ino)).await?; - - // Check if this was the last link to the inode. POSIX: while - // open handles exist the nlink=0 rows stay alive; the last - // handle drop queues the orphan for process_deferred_reaps. - let link_count = self.get_link_count(&conn, ino).await?; - let removed = link_count == 0 && !self.open_inodes.defer_reap_if_open(ino); - if removed { - // Delete data blocks - let mut stmt = conn - .prepare_cached("DELETE FROM fs_data WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - - // Delete symlink if exists - let mut stmt = conn - .prepare_cached("DELETE FROM fs_symlink WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - - // Delete inode - let mut stmt = conn - .prepare_cached("DELETE FROM fs_inode WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - } - - Ok((ino, removed)) - } - .await; - - match result { - Ok((ino, removed)) => { - txn.commit().await?; - if removed { - // Tier Four: discard any pending writes the batcher might - // still hold for this inode. The drains tolerate a deleted - // inode (NotFound is skipped, never inserted as orphan - // `fs_data` rows), so dropping the moot ranges after the - // commit keeps the overlay clean without risking data loss - // on a rolled-back unlink. - if let Some(batcher) = &self.write_batcher { - batcher.discard_pending(ino); - } - } - self.invalidate_dentry(parent_ino, name); - self.invalidate_parent_attr(parent_ino); - self.invalidate_attr(ino); - self.cache_negative_dentry(parent_ino, name); - Ok(()) - } - Err(error) => { - let _ = txn.rollback().await; - Err(error) - } - } - } - - async fn rmdir(&self, parent_ino: i64, name: &str) -> Result<()> { - if name.len() > MAX_NAME_LEN { - return Err(FsError::NameTooLong.into()); - } - self.process_deferred_reaps().await?; - let conn = self.pool.get_connection().await?; - // BEGIN IMMEDIATE: see `unlink` — never race the batcher's drain - // transactions with autocommit metadata writes. - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let result: Result = async { - // Look up the child inode - let ino = self - .lookup_child(&conn, parent_ino, name) - .await? - .ok_or(FsError::NotFound)?; - - if ino == ROOT_INO { - return Err(FsError::RootOperation.into()); - } - - // Check if it's a directory - let mut stmt = conn - .prepare_cached("SELECT mode FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if let Some(row) = rows.next().await? { - let mode = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32; - - if (mode & S_IFMT) != super::S_IFDIR { - return Err(FsError::NotADirectory.into()); - } - } else { - return Err(FsError::NotFound.into()); - } - - // Check if directory is empty - let mut stmt = conn - .prepare_cached("SELECT COUNT(*) FROM fs_dentry WHERE parent_ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if let Some(row) = rows.next().await? { - let count = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - if count > 0 { - return Err(FsError::NotEmpty.into()); - } - } - - // Delete the directory entry - let mut stmt = conn - .prepare_cached("DELETE FROM fs_dentry WHERE parent_ino = ? AND name = ?") - .await?; - stmt.execute((parent_ino, name)).await?; - - // Decrement link count on removed directory - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - - // Decrement parent nlink (removed directory's ".." link) and update timestamps - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - let mut stmt = conn - .prepare_cached( - "UPDATE fs_inode SET nlink = nlink - 1, ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", - ) - .await?; - stmt.execute((now_secs, now_secs, now_nsec, now_nsec, parent_ino)) - .await?; - - // Delete inode if no more links - let link_count = self.get_link_count(&conn, ino).await?; - if link_count == 0 { - let mut stmt = conn - .prepare_cached("DELETE FROM fs_inode WHERE ino = ?") - .await?; - stmt.execute((ino,)).await?; - } - - Ok(ino) - } - .await; - - match result { - Ok(ino) => { - txn.commit().await?; - self.invalidate_dentry(parent_ino, name); - self.invalidate_parent_attr(parent_ino); - self.invalidate_attr(ino); - self.cache_negative_dentry(parent_ino, name); - Ok(()) - } - Err(error) => { - let _ = txn.rollback().await; - Err(error) - } - } - } - - async fn link(&self, ino: i64, newparent_ino: i64, newname: &str) -> Result { - if newname.len() > MAX_NAME_LEN { - return Err(FsError::NameTooLong.into()); - } - let conn = self.pool.get_connection().await?; - // BEGIN IMMEDIATE: see `unlink` — never race the batcher's drain - // transactions with autocommit metadata writes. - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - let result: Result = async { - // Check if source inode exists and is not a directory - let mut stmt = conn - .prepare_cached("SELECT mode FROM fs_inode WHERE ino = ?") - .await?; - let mut rows = stmt.query((ino,)).await?; - - if let Some(row) = rows.next().await? { - let mode = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0) as u32; - - if (mode & S_IFMT) == super::S_IFDIR { - return Err(FsError::IsADirectory.into()); - } - } else { - return Err(FsError::NotFound.into()); - } - - // Check if destination already exists - if self - .lookup_child(&conn, newparent_ino, newname) - .await? - .is_some() - { - return Err(FsError::AlreadyExists.into()); - } - - // Create directory entry pointing to the same inode - conn.execute( - "INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)", - (newname, newparent_ino, ino), - ) - .await?; - - // Increment link count and update ctime - let dur = SystemTime::now().duration_since(UNIX_EPOCH)?; - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - conn.execute( - "UPDATE fs_inode SET nlink = nlink + 1, ctime = ?, ctime_nsec = ? WHERE ino = ?", - (now_secs, now_nsec, ino), - ) - .await?; - - // Update parent directory ctime and mtime - conn.execute( - "UPDATE fs_inode SET ctime = ?, mtime = ?, ctime_nsec = ?, mtime_nsec = ? WHERE ino = ?", - (now_secs, now_secs, now_nsec, now_nsec, newparent_ino), - ) - .await?; - - // Return updated stats (drop the cached pre-link attr so the read - // below reflects the nlink/ctime updates made in this transaction). - self.invalidate_attr(ino); - self.getattr_with_conn(&conn, ino) - .await? - .ok_or(FsError::NotFound.into()) - } - .await; - - match result { - Ok(stats) => { - txn.commit().await?; - // Populate dentry cache only after the transaction is durable. - self.cache_dentry(newparent_ino, newname, ino); - self.invalidate_parent_attr(newparent_ino); - self.invalidate_attr(ino); - Ok(stats) - } - Err(error) => { - let _ = txn.rollback().await; - self.invalidate_attr(ino); - Err(error) - } - } - } - - async fn rename( - &self, - oldparent_ino: i64, - oldname: &str, - newparent_ino: i64, - newname: &str, - ) -> Result<()> { - if newname.len() > MAX_NAME_LEN { - return Err(FsError::NameTooLong.into()); - } - self.process_deferred_reaps().await?; - let conn = self.pool.get_connection().await?; - - // Get source inode - let src_ino = self - .lookup_child(&conn, oldparent_ino, oldname) - .await? - .ok_or(FsError::NotFound)?; - - if src_ino == ROOT_INO { - return Err(FsError::RootOperation.into()); - } - - // Get source stats to check if it's a directory - let src_stats = self - .getattr_with_conn(&conn, src_ino) - .await? - .ok_or(FsError::NotFound)?; - - let txn = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate).await?; - - let result: Result> = async { - let mut replaced_dst_ino = None; - // Check if destination exists - if let Some(dst_ino) = self.lookup_child(&conn, newparent_ino, newname).await? { - replaced_dst_ino = Some(dst_ino); - let dst_stats = self.getattr_with_conn(&conn, dst_ino).await?.ok_or(FsError::NotFound)?; - - // Can't replace directory with non-directory - if dst_stats.is_directory() && !src_stats.is_directory() { - return Err(FsError::IsADirectory.into()); - } - - // Can't replace non-directory with directory - if !dst_stats.is_directory() && src_stats.is_directory() { - return Err(FsError::NotADirectory.into()); - } - - // If destination is directory, it must be empty - if dst_stats.is_directory() { - let mut stmt = conn - .prepare_cached("SELECT COUNT(*) FROM fs_dentry WHERE parent_ino = ?") - .await?; - let mut rows = stmt.query((dst_ino,)).await?; - - if let Some(row) = rows.next().await? { - let count = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(0); - if count > 0 { - return Err(FsError::NotEmpty.into()); - } - } - } - - // Remove destination entry - let mut stmt = conn - .prepare_cached("DELETE FROM fs_dentry WHERE parent_ino = ? AND name = ?") - .await?; - stmt.execute((newparent_ino, newname)).await?; - - // Decrement link count and update ctime on destination inode - let dur_dec = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default(); - let now_dec = dur_dec.as_secs() as i64; - let now_dec_nsec = dur_dec.subsec_nanos() as i64; - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET nlink = nlink - 1, ctime = ?, ctime_nsec = ? WHERE ino = ?") - .await?; - stmt.execute((now_dec, now_dec_nsec, dst_ino)).await?; - - // Clean up destination inode if no more links (deferred while - // open handles exist — see OpenInodes) - let link_count = self.get_link_count(&conn, dst_ino).await?; - if link_count == 0 && !self.open_inodes.defer_reap_if_open(dst_ino) { - // Tier Four: see public `rename` for rationale — drop - // pending batched writes for the deleted inode so a - // subsequent batched drain doesn't INSERT into a - // missing fs_inode row. - if let Some(batcher) = &self.write_batcher { - batcher.discard_pending(dst_ino); - } - let mut stmt = conn - .prepare_cached("DELETE FROM fs_data WHERE ino = ?") - .await?; - stmt.execute((dst_ino,)).await?; - let mut stmt = conn - .prepare_cached("DELETE FROM fs_symlink WHERE ino = ?") - .await?; - stmt.execute((dst_ino,)).await?; - let mut stmt = conn - .prepare_cached("DELETE FROM fs_inode WHERE ino = ?") - .await?; - stmt.execute((dst_ino,)).await?; - } - } - - // Update the dentry: change parent and/or name - let mut stmt = conn - .prepare_cached( - "UPDATE fs_dentry SET parent_ino = ?, name = ? WHERE parent_ino = ? AND name = ?", - ) - .await?; - stmt.execute((newparent_ino, newname, oldparent_ino, oldname)) - .await?; - - // If renaming a directory across parents, adjust parent nlink counts - // (the ".." link moves from old parent to new parent) - if src_stats.is_directory() && oldparent_ino != newparent_ino { - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?") - .await?; - stmt.execute((oldparent_ino,)).await?; - - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?") - .await?; - stmt.execute((newparent_ino,)).await?; - } - - // Update ctime of the inode - let dur = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default(); - let now_secs = dur.as_secs() as i64; - let now_nsec = dur.subsec_nanos() as i64; - - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET ctime = ?, ctime_nsec = ? WHERE ino = ?") - .await?; - stmt.execute((now_secs, now_nsec, src_ino)).await?; - - // Update source parent directory timestamps - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?") - .await?; - stmt.execute((now_secs, now_secs, now_nsec, now_nsec, oldparent_ino)).await?; - - // Update destination parent directory timestamps - if newparent_ino != oldparent_ino { - let mut stmt = conn - .prepare_cached("UPDATE fs_inode SET mtime = ?, ctime = ?, mtime_nsec = ?, ctime_nsec = ? WHERE ino = ?") - .await?; - stmt.execute((now_secs, now_secs, now_nsec, now_nsec, newparent_ino)).await?; - } - - Ok(replaced_dst_ino) - } - .await; - - match result { - Ok(replaced_dst_ino) => { - txn.commit().await?; - - // Invalidate cache for source and destination - self.invalidate_dentry(oldparent_ino, oldname); - self.invalidate_dentry(newparent_ino, newname); - self.invalidate_attr(src_ino); - self.invalidate_parent_attr(oldparent_ino); - self.invalidate_parent_attr(newparent_ino); - if let Some(dst_ino) = replaced_dst_ino { - self.invalidate_attr(dst_ino); - } - - // Add exact post-rename namespace state to the caches. - if oldparent_ino != newparent_ino || oldname != newname { - self.cache_negative_dentry(oldparent_ino, oldname); - } - self.cache_dentry(newparent_ino, newname, src_ino); - - Ok(()) - } - Err(e) => { - let _ = txn.rollback().await; - Err(e) - } - } - } - - async fn statfs(&self) -> Result { - AgentFS::statfs(self).await - } - - async fn drain_inode_writes(&self, ino: i64) -> Result<()> { - AgentFS::drain_inode_writes(self, ino).await - } - - async fn drain_all(&self) -> Result<()> { - AgentFS::drain_all(self).await - } - - async fn finalize(&self) -> Result<()> { - AgentFS::finalize(self).await - } - - // `forget` deliberately uses the default no-op trait impl: a FORGET only - // drops the kernel's reference to the inode. Pending batched writes stay - // readable through the Tier-4 overlay and are committed by the batcher - // timer/bytes triggers, fsync, or finalize — committing them here issued - // one serial SQLite transaction per written file during clone workloads - // (the kernel FORGETs each file shortly after our post-write entry - // invalidation). -} - -#[cfg(test)] -mod tests { - use super::*; - use tempfile::tempdir; - - // Turso 0.5.x reports SQLite's standard numeric value for NORMAL. - const TURSO_OBSERVED_SYNCHRONOUS_NORMAL: i64 = 1; - - async fn create_test_fs() -> Result<(AgentFS, tempfile::TempDir)> { - let dir = tempdir()?; - let db_path = dir.path().join("test.db"); - let fs = AgentFS::new(db_path.to_str().unwrap()).await?; - Ok((fs, dir)) - } - - fn cached_attr(fs: &AgentFS, ino: i64) -> Option { - fs.attr_cache.get(ino) - } - - fn negative_cached(fs: &AgentFS, parent_ino: i64, name: &str) -> bool { - fs.negative_dentry_cache.contains(parent_ino, name) - } - - #[tokio::test] - async fn import_entries_builds_tree_with_correct_content_and_stats() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let big = vec![0xabu8; DEFAULT_INLINE_THRESHOLD + DEFAULT_CHUNK_SIZE + 17]; - let entries = vec![ - ImportEntry { - path: "sub".to_string(), - mode: S_IFDIR | 0o755, - data: Vec::new(), - }, - ImportEntry { - path: "sub/inner".to_string(), - mode: S_IFDIR | 0o755, - data: Vec::new(), - }, - ImportEntry { - path: "sub/small.txt".to_string(), - mode: S_IFREG | 0o644, - data: b"hello import".to_vec(), - }, - ImportEntry { - path: "sub/inner/big.bin".to_string(), - mode: S_IFREG | 0o755, - data: big.clone(), - }, - ImportEntry { - path: "sub/link".to_string(), - mode: S_IFLNK | 0o777, - data: b"small.txt".to_vec(), - }, - ]; - let opts = ImportOptions { - uid: 7, - gid: 9, - timestamp: (1_700_000_000, 123_456_789), - }; - let imported = fs.import_entries(ROOT_INO, &entries, &opts).await?; - assert_eq!(imported.len(), entries.len()); - - assert_eq!( - fs.read_file("/sub/small.txt").await?.unwrap(), - b"hello import" - ); - assert_eq!(fs.read_file("/sub/inner/big.bin").await?.unwrap(), big); - assert_eq!(fs.readlink("/sub/link").await?.unwrap(), "small.txt"); - - let small = fs.stat("/sub/small.txt").await?.unwrap(); - let reported = imported.iter().find(|e| e.path == "sub/small.txt").unwrap(); - assert_eq!(small.ino, reported.ino); - assert_eq!(small.size as u64, reported.size); - assert_eq!(small.mode, S_IFREG | 0o644); - assert_eq!((small.uid, small.gid), (7, 9)); - assert_eq!(small.mtime, 1_700_000_000); - assert_eq!(small.mtime_nsec, 123_456_789); - assert_eq!(small.ctime, 1_700_000_000); - - let big_stat = fs.stat("/sub/inner/big.bin").await?.unwrap(); - assert_eq!(big_stat.size as usize, big.len()); - assert_eq!(big_stat.mode, S_IFREG | 0o755); - - let sub = fs.stat("/sub").await?.unwrap(); - assert_eq!(sub.nlink, 3); // "." + parent link + inner - - // Duplicate import collides on the dentry UNIQUE constraint. - let dup = fs.import_entries(ROOT_INO, &entries[..1], &opts).await; - assert!(matches!(dup, Err(Error::Fs(FsError::AlreadyExists)))); - - Ok(()) - } - - #[tokio::test] - async fn attr_cache_invalidates_mutations_and_preserves_visibility() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - FileSystem::getattr(&fs, ROOT_INO).await?.unwrap(); - assert!(cached_attr(&fs, ROOT_INO).is_some()); - - let (created, file) = - FileSystem::create_file(&fs, ROOT_INO, "cache.txt", DEFAULT_FILE_MODE, 7, 9).await?; - let file_ino = created.ino; - assert!(cached_attr(&fs, ROOT_INO).is_none()); - assert_eq!(cached_attr(&fs, file_ino).unwrap().size, 0); - - file.pwrite(0, b"hello").await?; - assert!(cached_attr(&fs, file_ino).is_none()); - let written = FileSystem::getattr(&fs, file_ino).await?.unwrap(); - assert_eq!(written.size, 5); - assert_eq!(cached_attr(&fs, file_ino).unwrap().size, 5); - - file.pwrite(5, b" world").await?; - let after_append = FileSystem::getattr(&fs, file_ino).await?.unwrap(); - assert_eq!(after_append.size, 11); - assert_eq!(file.pread(0, 11).await?, b"hello world"); - - file.truncate(5).await?; - assert!(cached_attr(&fs, file_ino).is_none()); - let truncated = FileSystem::getattr(&fs, file_ino).await?.unwrap(); - assert_eq!(truncated.size, 5); - assert_eq!(file.pread(0, 16).await?, b"hello"); - - FileSystem::chmod(&fs, file_ino, 0o600).await?; - assert!(cached_attr(&fs, file_ino).is_none()); - let chmodded = FileSystem::getattr(&fs, file_ino).await?.unwrap(); - assert_eq!(chmodded.mode & 0o7777, 0o600); - - FileSystem::chown(&fs, file_ino, Some(11), Some(13)).await?; - assert!(cached_attr(&fs, file_ino).is_none()); - let chowned = FileSystem::getattr(&fs, file_ino).await?.unwrap(); - assert_eq!((chowned.uid, chowned.gid), (11, 13)); - - FileSystem::utimens( - &fs, - file_ino, - TimeChange::Set(1_700_000_001, 123), - TimeChange::Set(1_700_000_002, 456), - ) - .await?; - assert!(cached_attr(&fs, file_ino).is_none()); - let timestamped = FileSystem::getattr(&fs, file_ino).await?.unwrap(); - assert_eq!( - (timestamped.mtime, timestamped.mtime_nsec), - (1_700_000_002, 456) - ); - - FileSystem::getattr(&fs, ROOT_INO).await?.unwrap(); - let linked = FileSystem::link(&fs, file_ino, ROOT_INO, "hard.txt").await?; - assert!(cached_attr(&fs, ROOT_INO).is_none()); - assert_eq!(linked.nlink, 2); - assert_eq!( - FileSystem::lookup(&fs, ROOT_INO, "hard.txt") - .await? - .unwrap() - .ino, - file_ino - ); - - FileSystem::getattr(&fs, ROOT_INO).await?.unwrap(); - let symlink = FileSystem::symlink(&fs, ROOT_INO, "cache.link", "cache.txt", 11, 13).await?; - assert!(cached_attr(&fs, ROOT_INO).is_none()); - assert!(symlink.is_symlink()); - assert_eq!( - FileSystem::readlink(&fs, symlink.ino).await?, - Some("cache.txt".to_string()) - ); - - FileSystem::getattr(&fs, ROOT_INO).await?.unwrap(); - let dir = FileSystem::mkdir(&fs, ROOT_INO, "dir", 0o755, 11, 13).await?; - assert!(cached_attr(&fs, ROOT_INO).is_none()); - assert!(cached_attr(&fs, dir.ino).is_some()); - FileSystem::getattr(&fs, ROOT_INO).await?.unwrap(); - FileSystem::rmdir(&fs, ROOT_INO, "dir").await?; - assert!(cached_attr(&fs, ROOT_INO).is_none()); - assert!(cached_attr(&fs, dir.ino).is_none()); - assert!(FileSystem::lookup(&fs, ROOT_INO, "dir").await?.is_none()); - - FileSystem::getattr(&fs, file_ino).await?.unwrap(); - FileSystem::getattr(&fs, ROOT_INO).await?.unwrap(); - FileSystem::rename(&fs, ROOT_INO, "cache.txt", ROOT_INO, "renamed.txt").await?; - assert!(cached_attr(&fs, file_ino).is_none()); - assert!(cached_attr(&fs, ROOT_INO).is_none()); - assert!(FileSystem::lookup(&fs, ROOT_INO, "cache.txt") - .await? - .is_none()); - assert_eq!( - FileSystem::lookup(&fs, ROOT_INO, "renamed.txt") - .await? - .unwrap() - .ino, - file_ino - ); - assert_eq!(file.pread(0, 16).await?, b"hello"); - - FileSystem::getattr(&fs, file_ino).await?.unwrap(); - FileSystem::unlink(&fs, ROOT_INO, "hard.txt").await?; - assert!(cached_attr(&fs, file_ino).is_none()); - let single_link = FileSystem::getattr(&fs, file_ino).await?.unwrap(); - assert_eq!(single_link.nlink, 1); - - FileSystem::unlink(&fs, ROOT_INO, "renamed.txt").await?; - assert!(cached_attr(&fs, file_ino).is_none()); - assert!(FileSystem::lookup(&fs, ROOT_INO, "renamed.txt") - .await? - .is_none()); - - Ok(()) - } - - #[tokio::test] - async fn negative_dentry_cache_invalidates_on_namespace_mutations() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - assert!(FileSystem::lookup(&fs, ROOT_INO, "missing.txt") - .await? - .is_none()); - assert!(negative_cached(&fs, ROOT_INO, "missing.txt")); - - let (created, _file) = - FileSystem::create_file(&fs, ROOT_INO, "missing.txt", DEFAULT_FILE_MODE, 7, 9).await?; - assert!(!negative_cached(&fs, ROOT_INO, "missing.txt")); - assert_eq!( - FileSystem::lookup(&fs, ROOT_INO, "missing.txt") - .await? - .unwrap() - .ino, - created.ino - ); - - FileSystem::rename(&fs, ROOT_INO, "missing.txt", ROOT_INO, "renamed.txt").await?; - assert!(negative_cached(&fs, ROOT_INO, "missing.txt")); - assert!(!negative_cached(&fs, ROOT_INO, "renamed.txt")); - assert!(FileSystem::lookup(&fs, ROOT_INO, "missing.txt") - .await? - .is_none()); - assert_eq!( - FileSystem::lookup(&fs, ROOT_INO, "renamed.txt") - .await? - .unwrap() - .ino, - created.ino - ); - - FileSystem::unlink(&fs, ROOT_INO, "renamed.txt").await?; - assert!(negative_cached(&fs, ROOT_INO, "renamed.txt")); - assert!(FileSystem::lookup(&fs, ROOT_INO, "renamed.txt") - .await? - .is_none()); - - assert!(FileSystem::lookup(&fs, ROOT_INO, "negdir").await?.is_none()); - assert!(negative_cached(&fs, ROOT_INO, "negdir")); - FileSystem::mkdir(&fs, ROOT_INO, "negdir", 0o755, 7, 9).await?; - assert!(!negative_cached(&fs, ROOT_INO, "negdir")); - FileSystem::rmdir(&fs, ROOT_INO, "negdir").await?; - assert!(negative_cached(&fs, ROOT_INO, "negdir")); - - Ok(()) - } - - async fn read_pragma_i64(conn: &Connection, sql: &str) -> i64 { - let mut rows = conn.query(sql, ()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - row.get_value(0) - .ok() - .and_then(|value| match value { - Value::Integer(value) => Some(value), - Value::Text(value) => value.parse().ok(), - _ => None, - }) - .unwrap() - } - - async fn read_pragma_text(conn: &Connection, sql: &str) -> String { - let mut rows = conn.query(sql, ()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - row.get_value(0) - .ok() - .and_then(|value| match value { - Value::Text(value) => Some(value.clone()), - Value::Integer(value) => Some(value.to_string()), - _ => None, - }) - .unwrap() - } - - // ==================== Chunk Size Boundary Tests ==================== - - #[tokio::test] - async fn test_file_smaller_than_chunk_size() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Write a file smaller than chunk_size (100 bytes) - let data = vec![0u8; 100]; - let (_, file) = fs - .create_file("/small.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - - // Read it back - let read_data = fs.read_file("/small.txt").await?.unwrap(); - assert_eq!(read_data.len(), 100); - assert_eq!(read_data, data); - - // Verify inline storage avoids chunks - let ino = fs.resolve_path("/small.txt").await?.unwrap(); - let chunk_count = fs.get_chunk_count(ino).await?; - assert_eq!(chunk_count, 0); - let (storage_kind, data_inline) = fs.get_storage_state(ino).await?; - assert_eq!(storage_kind, STORAGE_INLINE); - assert_eq!(data_inline, Some(data)); - - Ok(()) - } - - #[tokio::test] - async fn test_file_exactly_chunk_size() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Write exactly chunk_size bytes - let chunk_size = fs.chunk_size(); - let data: Vec = (0..chunk_size).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs - .create_file("/exact.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - - // Read it back - let read_data = fs.read_file("/exact.txt").await?.unwrap(); - assert_eq!(read_data.len(), chunk_size); - assert_eq!(read_data, data); - - // Verify only 1 chunk was created - let ino = fs.resolve_path("/exact.txt").await?.unwrap(); - let chunk_count = fs.get_chunk_count(ino).await?; - assert_eq!(chunk_count, 1); - - Ok(()) - } - - #[tokio::test] - async fn test_file_one_byte_over_chunk_size() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Write chunk_size + 1 bytes - let chunk_size = fs.chunk_size(); - let data: Vec = (0..=chunk_size).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs - .create_file("/overflow.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - - // Read it back - let read_data = fs.read_file("/overflow.txt").await?.unwrap(); - assert_eq!(read_data.len(), chunk_size + 1); - assert_eq!(read_data, data); - - // Verify 2 chunks were created - let ino = fs.resolve_path("/overflow.txt").await?.unwrap(); - let chunk_count = fs.get_chunk_count(ino).await?; - assert_eq!(chunk_count, 2); - - Ok(()) - } - - #[tokio::test] - async fn test_file_spanning_multiple_chunks() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Write ~2.5 chunks worth of data - let chunk_size = fs.chunk_size(); - let data_size = chunk_size * 2 + chunk_size / 2; - let data: Vec = (0..data_size).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs - .create_file("/multi.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - - // Read it back - let read_data = fs.read_file("/multi.txt").await?.unwrap(); - assert_eq!(read_data.len(), data_size); - assert_eq!(read_data, data); - - // Verify 3 chunks were created - let ino = fs.resolve_path("/multi.txt").await?.unwrap(); - let chunk_count = fs.get_chunk_count(ino).await?; - assert_eq!(chunk_count, 3); - - Ok(()) - } - - // ==================== Data Integrity Tests ==================== - - #[tokio::test] - async fn test_roundtrip_byte_for_byte() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create data that spans chunk boundaries with identifiable patterns - let chunk_size = fs.chunk_size(); - let data_size = chunk_size * 3 + 123; // Odd size spanning 4 chunks - - let data: Vec = (0..data_size).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs - .create_file("/roundtrip.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - - let read_data = fs.read_file("/roundtrip.bin").await?.unwrap(); - assert_eq!(read_data.len(), data_size); - assert_eq!(read_data, data, "Data mismatch after roundtrip"); - - Ok(()) - } - - #[tokio::test] - async fn test_binary_data_with_null_bytes() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let chunk_size = fs.chunk_size(); - // Create data with null bytes at chunk boundaries - let mut data = vec![0u8; chunk_size * 2 + 100]; - // Put nulls at the chunk boundary - data[chunk_size - 1] = 0; - data[chunk_size] = 0; - data[chunk_size + 1] = 0; - // Put some non-null bytes around - data[chunk_size - 2] = 0xFF; - data[chunk_size + 2] = 0xFF; - - let (_, file) = fs - .create_file("/nulls.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - let read_data = fs.read_file("/nulls.bin").await?.unwrap(); - - assert_eq!(read_data, data, "Null bytes at chunk boundary corrupted"); - - Ok(()) - } - - #[tokio::test] - async fn test_chunk_ordering() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let chunk_size = fs.chunk_size(); - // Create sequential bytes spanning multiple chunks - let data_size = chunk_size * 5; - let data: Vec = (0..data_size).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs - .create_file("/sequential.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - - let read_data = fs.read_file("/sequential.bin").await?.unwrap(); - - // Verify every byte is in the correct position - for (i, (&expected, &actual)) in data.iter().zip(read_data.iter()).enumerate() { - assert_eq!( - expected, actual, - "Byte mismatch at position {}: expected {}, got {}", - i, expected, actual - ); - } - - Ok(()) - } - - // ==================== Edge Case Tests ==================== - - #[tokio::test] - async fn test_empty_file() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Write empty file - let (_, file) = fs - .create_file("/empty.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &[]).await?; - - // Read it back - let read_data = fs.read_file("/empty.txt").await?.unwrap(); - assert!(read_data.is_empty()); - - // Verify 0 chunks were created - let ino = fs.resolve_path("/empty.txt").await?.unwrap(); - let chunk_count = fs.get_chunk_count(ino).await?; - assert_eq!(chunk_count, 0); - - // Verify size is 0 - let stats = fs.stat("/empty.txt").await?.unwrap(); - assert_eq!(stats.size, 0); - - let (storage_kind, data_inline) = fs.get_storage_state(ino).await?; - assert_eq!(storage_kind, STORAGE_INLINE); - assert_eq!(data_inline, Some(Vec::new())); - - Ok(()) - } - - #[tokio::test] - async fn test_inline_small_file_and_overwrite() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let (_, file) = fs - .create_file("/inline.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, b"hello world").await?; - file.pwrite(6, b"agent").await?; - - let ino = fs.resolve_path("/inline.txt").await?.unwrap(); - assert_eq!(fs.read_file("/inline.txt").await?.unwrap(), b"hello agent"); - assert_eq!(fs.get_chunk_count(ino).await?, 0); - let (storage_kind, data_inline) = fs.get_storage_state(ino).await?; - assert_eq!(storage_kind, STORAGE_INLINE); - assert_eq!(data_inline, Some(b"hello agent".to_vec())); - - Ok(()) - } - - #[tokio::test] - async fn test_inline_transitions_to_chunked_over_threshold() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let prefix = vec![1u8; DEFAULT_INLINE_THRESHOLD]; - let suffix = vec![2u8; 32]; - let (_, file) = fs - .create_file("/transition.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &prefix).await?; - - let ino = fs.resolve_path("/transition.bin").await?.unwrap(); - assert_eq!(fs.get_storage_state(ino).await?.0, STORAGE_INLINE); - - file.pwrite(DEFAULT_INLINE_THRESHOLD as u64, &suffix) - .await?; - - let mut expected = prefix; - expected.extend_from_slice(&suffix); - assert_eq!(fs.read_file("/transition.bin").await?.unwrap(), expected); - assert_eq!(fs.get_storage_state(ino).await?, (STORAGE_CHUNKED, None)); - assert_eq!(fs.get_chunk_count(ino).await?, 1); - - Ok(()) - } - - #[tokio::test] - async fn test_sparse_write_transitions_inline_to_chunked() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let (_, file) = fs - .create_file("/sparse.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, b"abc").await?; - file.pwrite(10, b"z").await?; - - let ino = fs.resolve_path("/sparse.bin").await?.unwrap(); - assert_eq!(fs.get_storage_state(ino).await?, (STORAGE_CHUNKED, None)); - assert_eq!(fs.get_chunk_count(ino).await?, 1); - - let mut expected = b"abc".to_vec(); - expected.resize(10, 0); - expected.push(b'z'); - let read_back = file.pread(0, expected.len() as u64).await?; - assert_eq!(read_back, expected); - - Ok(()) - } - - #[tokio::test] - async fn test_chunked_truncate_back_to_inline_when_dense() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let data = vec![7u8; DEFAULT_INLINE_THRESHOLD + 1]; - let (_, file) = fs - .create_file("/dense.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - - let ino = fs.resolve_path("/dense.bin").await?.unwrap(); - assert_eq!(fs.get_storage_state(ino).await?, (STORAGE_CHUNKED, None)); - - file.truncate(128).await?; - - assert_eq!(fs.read_file("/dense.bin").await?.unwrap(), vec![7u8; 128]); - assert_eq!(fs.get_chunk_count(ino).await?, 0); - let (storage_kind, data_inline) = fs.get_storage_state(ino).await?; - assert_eq!(storage_kind, STORAGE_INLINE); - assert_eq!(data_inline, Some(vec![7u8; 128])); - - Ok(()) - } - - #[tokio::test] - async fn test_sparse_chunked_truncate_below_threshold_stays_chunked() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let (_, file) = fs - .create_file("/sparse-truncate.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(fs.chunk_size() as u64 + 8, b"tail").await?; - // Tier Four: ensure the sparse write reaches SQLite as chunked - // storage before we truncate; otherwise truncate_pending strips it - // in memory and the file never transitions out of INLINE. - file.fsync().await?; - file.truncate(4).await?; - - let ino = fs.resolve_path("/sparse-truncate.bin").await?.unwrap(); - assert_eq!(fs.get_storage_state(ino).await?, (STORAGE_CHUNKED, None)); - assert_eq!(file.pread(0, 4).await?, vec![0u8; 4]); - - Ok(()) - } - - #[tokio::test] - async fn test_64k_chunk_boundary_uses_single_default_chunk() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - assert_eq!(fs.chunk_size(), 64 * 1024); - let data: Vec = (0..fs.chunk_size()).map(|i| (i % 251) as u8).collect(); - let (_, file) = fs - .create_file("/boundary.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - - let ino = fs.resolve_path("/boundary.bin").await?.unwrap(); - assert_eq!(fs.get_storage_state(ino).await?, (STORAGE_CHUNKED, None)); - assert_eq!(fs.get_chunk_count(ino).await?, 1); - assert_eq!( - file.pread((fs.chunk_size() - 8) as u64, 16).await?, - data[fs.chunk_size() - 8..].to_vec() - ); - - Ok(()) - } - - #[tokio::test] - async fn test_overwrite_existing_file() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let chunk_size = fs.chunk_size(); - - // Write initial large file (3 chunks) - let initial_data: Vec = (0..chunk_size * 3).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs - .create_file("/overwrite.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &initial_data).await?; - - let ino = fs.resolve_path("/overwrite.txt").await?.unwrap(); - let initial_chunk_count = fs.get_chunk_count(ino).await?; - assert_eq!(initial_chunk_count, 3); - - // Overwrite with smaller file (1 chunk) - let new_data = vec![42u8; 100]; - fs.truncate("/overwrite.txt", 0).await?; - let file = fs.open("/overwrite.txt").await?; - file.pwrite(0, &new_data).await?; - - // Verify old chunks are gone and new data is correct - let read_data = fs.read_file("/overwrite.txt").await?.unwrap(); - assert_eq!(read_data, new_data); - - let new_chunk_count = fs.get_chunk_count(ino).await?; - assert_eq!(new_chunk_count, 0); - let (storage_kind, data_inline) = fs.get_storage_state(ino).await?; - assert_eq!(storage_kind, STORAGE_INLINE); - assert_eq!(data_inline, Some(new_data)); - - // Verify size is updated - let stats = fs.stat("/overwrite.txt").await?.unwrap(); - assert_eq!(stats.size, 100); - - Ok(()) - } - - #[tokio::test] - async fn test_overwrite_with_larger_file() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let chunk_size = fs.chunk_size(); - - // Write initial small file (1 chunk) - let initial_data = vec![1u8; 100]; - let (_, file) = fs.create_file("/grow.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &initial_data).await?; - - let ino = fs.resolve_path("/grow.txt").await?.unwrap(); - assert_eq!(fs.get_chunk_count(ino).await?, 0); - assert_eq!(fs.get_storage_state(ino).await?.0, STORAGE_INLINE); - - // Overwrite with larger file (3 chunks) - let new_data: Vec = (0..chunk_size * 3).map(|i| (i % 256) as u8).collect(); - fs.truncate("/grow.txt", 0).await?; - let file = fs.open("/grow.txt").await?; - file.pwrite(0, &new_data).await?; - - // Verify data is correct - let read_data = fs.read_file("/grow.txt").await?.unwrap(); - assert_eq!(read_data, new_data); - assert_eq!(fs.get_chunk_count(ino).await?, 3); - - Ok(()) - } - - #[tokio::test] - async fn test_very_large_file() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Write 1MB file - let data_size = 1024 * 1024; - let data: Vec = (0..data_size).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs - .create_file("/large.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - - let read_data = fs.read_file("/large.bin").await?.unwrap(); - assert_eq!(read_data.len(), data_size); - assert_eq!(read_data, data); - - // Verify correct number of chunks - let chunk_size = fs.chunk_size(); - let expected_chunks = data_size.div_ceil(chunk_size); - let ino = fs.resolve_path("/large.bin").await?.unwrap(); - let actual_chunks = fs.get_chunk_count(ino).await? as usize; - assert_eq!(actual_chunks, expected_chunks); - - Ok(()) - } - - // ==================== Configuration Tests ==================== - - #[tokio::test] - async fn test_default_chunk_size() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - assert_eq!(fs.chunk_size(), DEFAULT_CHUNK_SIZE); - assert_eq!(fs.chunk_size(), 65536); - assert_eq!(fs.inline_threshold(), DEFAULT_INLINE_THRESHOLD); - assert_eq!(fs.inline_threshold(), 16384); - - Ok(()) - } - - #[tokio::test] - async fn test_chunk_size_accessor() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let chunk_size = fs.chunk_size(); - assert!(chunk_size > 0); - - // Write data and verify chunks match expected based on chunk_size - let data = vec![0u8; chunk_size * 2 + 1]; - let (_, file) = fs.create_file("/test.bin", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - - let ino = fs.resolve_path("/test.bin").await?.unwrap(); - let chunk_count = fs.get_chunk_count(ino).await?; - assert_eq!(chunk_count, 3); - - Ok(()) - } - - #[tokio::test] - async fn test_config_persistence() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Query fs_config table directly - let conn = fs.pool.get_connection().await?; - let mut rows = conn - .query("SELECT value FROM fs_config WHERE key = 'chunk_size'", ()) - .await?; - - let row = rows.next().await?.expect("chunk_size config should exist"); - let value = row - .get_value(0) - .ok() - .and_then(|v| match v { - Value::Text(s) => Some(s.clone()), - _ => None, - }) - .expect("chunk_size should be a text value"); - - assert_eq!(value, "65536"); - - let mut rows = conn - .query( - "SELECT value FROM fs_config WHERE key = 'inline_threshold'", - (), - ) - .await?; - let row = rows - .next() - .await? - .expect("inline_threshold config should exist"); - let value = row - .get_value(0) - .ok() - .and_then(|v| match v { - Value::Text(s) => Some(s.clone()), - _ => None, - }) - .expect("inline_threshold should be a text value"); - - assert_eq!(value, "16384"); - - let mut rows = conn - .query( - "SELECT value FROM fs_config WHERE key = 'schema_version'", - (), - ) - .await?; - let row = rows - .next() - .await? - .expect("schema_version config should exist"); - let value = row - .get_value(0) - .ok() - .and_then(|v| match v { - Value::Text(s) => Some(s.clone()), - _ => None, - }) - .expect("schema_version should be a text value"); - - assert_eq!(value, "0.5"); - - Ok(()) - } - - #[tokio::test] - async fn test_v04_database_is_rejected_without_inline_migration() -> Result<()> { - let dir = tempdir()?; - let db_path = dir.path().join("legacy-v04.db"); - - { - let db = Builder::new_local(db_path.to_str().unwrap()) - .build() - .await?; - let conn = db.connect()?; - conn.execute( - "CREATE TABLE fs_config (key TEXT PRIMARY KEY, value TEXT NOT NULL)", - (), - ) - .await?; - conn.execute( - "INSERT INTO fs_config (key, value) VALUES ('schema_version', '0.4')", - (), - ) - .await?; - conn.execute( - "CREATE TABLE fs_inode ( - ino INTEGER PRIMARY KEY AUTOINCREMENT, - mode INTEGER NOT NULL, - nlink INTEGER NOT NULL DEFAULT 0, - uid INTEGER NOT NULL DEFAULT 0, - gid INTEGER NOT NULL DEFAULT 0, - size INTEGER NOT NULL DEFAULT 0, - atime INTEGER NOT NULL, - mtime INTEGER NOT NULL, - ctime INTEGER NOT NULL, - rdev INTEGER NOT NULL DEFAULT 0, - atime_nsec INTEGER NOT NULL DEFAULT 0, - mtime_nsec INTEGER NOT NULL DEFAULT 0, - ctime_nsec INTEGER NOT NULL DEFAULT 0 - )", - (), - ) - .await?; - } - - let result = - crate::AgentFS::open(crate::AgentFSOptions::with_path(db_path.to_string_lossy())).await; - match result { - Err(Error::SchemaVersionMismatch { found, expected }) => { - assert_eq!(found, "0.4"); - assert_eq!(expected, "0.5"); - } - Err(err) => panic!("expected schema version mismatch, got {err}"), - Ok(_) => panic!("legacy v0.4 database should not open as v0.5"), - } - - Ok(()) - } - - #[tokio::test] - async fn test_file_backed_connections_use_production_pragmas() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let conn1 = fs.pool.get_connection().await?; - let conn2 = fs.pool.get_connection().await?; - - for conn in [&conn1, &conn2] { - assert_eq!( - read_pragma_i64(conn, "PRAGMA synchronous").await, - TURSO_OBSERVED_SYNCHRONOUS_NORMAL - ); - assert_eq!(read_pragma_i64(conn, "PRAGMA busy_timeout").await, 5000); - assert_eq!( - read_pragma_text(conn, "PRAGMA journal_mode") - .await - .to_lowercase(), - "wal" - ); - } - - Ok(()) - } - - #[tokio::test] - async fn test_file_backed_options_issue_durable_baseline_sql() { - let options = file_backed_connection_pool_options(); - - assert_eq!(options.max_connections, FILE_BACKED_MAX_CONNECTIONS); - assert_eq!(options.setup_sql[0], BUSY_TIMEOUT_SQL); - assert!(options.setup_sql.iter().any(|sql| sql == WAL_MODE_SQL)); - assert!(options - .setup_sql - .iter() - .any(|sql| sql == BASELINE_SYNCHRONOUS_SQL)); - assert!(!options - .setup_sql - .iter() - .any(|sql| sql == "PRAGMA synchronous = OFF")); - } - - #[tokio::test] - async fn test_file_backed_agentfs_concurrent_operations_complete() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (_, file) = fs.create_file("/seed.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, b"seed").await?; - - let mut handles = Vec::new(); - for worker in 0..8 { - let fs = fs.clone(); - handles.push(tokio::spawn(async move { - for iteration in 0..5 { - let data = fs.read_file("/seed.txt").await?.unwrap(); - assert_eq!(data, b"seed"); - - let path = format!("/worker-{worker}-{iteration}"); - fs.mkdir(&path, 0, 0).await?; - } - Ok::<(), Error>(()) - })); - } - - for handle in handles { - handle.await.unwrap()?; - } - - Ok(()) - } - - #[tokio::test] - async fn test_fsync_restores_synchronous_normal() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let conn = fs.pool.get_connection().await?; - conn.execute("PRAGMA synchronous = OFF", ()).await?; - drop(conn); - - fs.fsync("/").await?; - - let conn = fs.pool.get_connection().await?; - assert_eq!( - read_pragma_i64(&conn, "PRAGMA synchronous").await, - TURSO_OBSERVED_SYNCHRONOUS_NORMAL - ); - - Ok(()) - } - - #[tokio::test] - async fn test_file_fsync_restores_synchronous_normal() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (_, file) = fs - .create_file("/fsync.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - - let conn = fs.pool.get_connection().await?; - conn.execute("PRAGMA synchronous = OFF", ()).await?; - drop(conn); - - file.fsync().await?; - - let conn = fs.pool.get_connection().await?; - assert_eq!( - read_pragma_i64(&conn, "PRAGMA synchronous").await, - TURSO_OBSERVED_SYNCHRONOUS_NORMAL - ); - - Ok(()) - } - - // ==================== Schema Tests ==================== - - #[tokio::test] - async fn test_chunk_index_uniqueness() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Write a file to create chunks - let chunk_size = fs.chunk_size(); - let data = vec![0u8; chunk_size * 2]; - let (_, file) = fs - .create_file("/unique.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - - let ino = fs.resolve_path("/unique.txt").await?.unwrap(); - // Tier Four: pwrite is async-batched; drain so fs_data is populated - // before we probe its primary-key constraint. - fs.drain_inode_writes(ino).await?; - - // Try to insert a duplicate chunk - should fail due to PRIMARY KEY constraint - let conn = fs.pool.get_connection().await?; - let result = conn - .execute( - "INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, 0, ?)", - (ino, vec![1u8; 10]), - ) - .await; - - assert!(result.is_err(), "Duplicate chunk_index should be rejected"); - - Ok(()) - } - - #[tokio::test] - async fn test_chunk_ordering_in_database() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let chunk_size = fs.chunk_size(); - // Create 5 chunks with identifiable data - let data_size = chunk_size * 5; - let data: Vec = (0..data_size).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs - .create_file("/ordered.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - - let ino = fs.resolve_path("/ordered.bin").await?.unwrap(); - // Tier Four: drain so fs_data rows are present for the SELECT below. - fs.drain_inode_writes(ino).await?; - - // Query chunks in order - let conn = fs.pool.get_connection().await?; - let mut rows = conn - .query( - "SELECT chunk_index FROM fs_data WHERE ino = ? ORDER BY chunk_index", - (ino,), - ) - .await?; - - let mut indices = Vec::new(); - while let Some(row) = rows.next().await? { - let idx = row - .get_value(0) - .ok() - .and_then(|v| v.as_integer().copied()) - .unwrap_or(-1); - indices.push(idx); - } - - assert_eq!(indices, vec![0, 1, 2, 3, 4]); - - Ok(()) - } - - // ==================== Cleanup Tests ==================== - - #[tokio::test] - async fn test_delete_file_removes_all_chunks() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let chunk_size = fs.chunk_size(); - // Create multi-chunk file - let data = vec![0u8; chunk_size * 4]; - let (_, file) = fs - .create_file("/deleteme.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &data).await?; - - let ino = fs.resolve_path("/deleteme.txt").await?.unwrap(); - assert_eq!(fs.get_chunk_count(ino).await?, 4); - - // Close the handle first: with it open, deletion is deferred (POSIX - // unlink-while-open) and the chunks legitimately survive the remove. - drop(file); - - // Delete the file - fs.remove("/deleteme.txt").await?; - - // Verify all chunks are gone - let conn = fs.pool.get_connection().await?; - let mut rows = conn - .query("SELECT COUNT(*) FROM fs_data WHERE ino = ?", (ino,)) - .await?; - - let count = rows - .next() - .await? - .and_then(|r| r.get_value(0).ok().and_then(|v| v.as_integer().copied())) - .unwrap_or(-1); - - assert_eq!(count, 0, "All chunks should be deleted"); - - Ok(()) - } - - async fn count_rows(fs: &AgentFS, table: &str, ino: i64) -> Result { - let conn = fs.pool.get_connection().await?; - let mut rows = conn - .query( - &format!("SELECT COUNT(*) FROM {table} WHERE ino = ?"), - (ino,), - ) - .await?; - Ok(rows - .next() - .await? - .and_then(|r| r.get_value(0).ok().and_then(|v| v.as_integer().copied())) - .unwrap_or(-1)) - } - - #[tokio::test] - async fn test_unlink_while_open_defers_reap() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let (stats, file) = fs - .create_file("/ghost.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - let ino = stats.ino; - file.pwrite(0, b"ghost").await?; - - FileSystem::unlink(&fs, ROOT_INO, "ghost.bin").await?; - - // POSIX: the open handle keeps the inode readable and writable. - assert!(fs.resolve_path("/ghost.bin").await?.is_none()); - assert_eq!(file.pread(0, 5).await?, b"ghost"); - file.pwrite(5, b"-more").await?; - assert_eq!(file.pread(0, 10).await?, b"ghost-more"); - assert_eq!(file.fstat().await?.nlink, 0); - assert_eq!(count_rows(&fs, "fs_inode", ino).await?, 1); - - // Last handle drop queues the reap; the next namespace mutation - // (or finalize) executes it. - drop(file); - fs.process_deferred_reaps().await?; - assert_eq!(count_rows(&fs, "fs_inode", ino).await?, 0); - assert_eq!(count_rows(&fs, "fs_data", ino).await?, 0); - - Ok(()) - } - - #[tokio::test] - async fn test_mount_sweep_reaps_crashed_orphans() -> Result<()> { - let dir = tempfile::tempdir()?; - let db_path = dir.path().join("sweep.db"); - let db_path = db_path.to_str().unwrap(); - - let ino = { - let fs = AgentFS::new(db_path).await?; - let (stats, file) = fs - .create_file("/ghost.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, b"ghost").await?; - file.drain_writes().await?; - FileSystem::unlink(&fs, ROOT_INO, "ghost.bin").await?; - // Simulate a crash: the guard never releases, so the orphan is - // neither queued nor reaped before the process "dies". - std::mem::forget(file); - stats.ino - }; - - let fs = AgentFS::new(db_path).await?; - assert_eq!(count_rows(&fs, "fs_inode", ino).await?, 0); - assert_eq!(count_rows(&fs, "fs_data", ino).await?, 0); - - Ok(()) - } - - #[tokio::test] - async fn test_multiple_files_different_sizes() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let chunk_size = fs.chunk_size(); - - // Create files of various sizes - let files = vec![ - ("/tiny.txt", 10), - ("/small.txt", chunk_size / 2), - ("/exact.txt", chunk_size), - ("/medium.txt", chunk_size * 2 + 100), - ("/large.txt", chunk_size * 5), - ]; - - for (path, size) in &files { - let data: Vec = (0..*size).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs.create_file(path, DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - } - - // Verify each file has correct data and chunk count - for (path, size) in &files { - let read_data = fs.read_file(path).await?.unwrap(); - assert_eq!(read_data.len(), *size, "Size mismatch for {}", path); - - let expected_data: Vec = (0..*size).map(|i| (i % 256) as u8).collect(); - assert_eq!(read_data, expected_data, "Data mismatch for {}", path); - - let expected_chunks = if *size <= fs.inline_threshold() { - 0 - } else { - size.div_ceil(chunk_size) - }; - let ino = fs.resolve_path(path).await?.unwrap(); - let actual_chunks = fs.get_chunk_count(ino).await? as usize; - assert_eq!( - actual_chunks, expected_chunks, - "Chunk count mismatch for {}", - path - ); - } - - Ok(()) - } - - #[tokio::test] - async fn test_pread_basic() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Write a file with known content - let data: Vec = (0..100).collect(); - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - - // Read from the beginning - let result = fs.pread("/test.txt", 0, 10).await?.unwrap(); - assert_eq!(result, &data[0..10]); - - // Read from the middle - let result = fs.pread("/test.txt", 50, 20).await?.unwrap(); - assert_eq!(result, &data[50..70]); - - // Read from near the end - let result = fs.pread("/test.txt", 90, 10).await?.unwrap(); - assert_eq!(result, &data[90..100]); - - Ok(()) - } - - #[tokio::test] - async fn test_pread_past_eof() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let data: Vec = (0..50).collect(); - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - - // Read starting past EOF should return empty - let result = fs.pread("/test.txt", 100, 10).await?.unwrap(); - assert!(result.is_empty()); - - // Read that extends past EOF should return only available data - let result = fs.pread("/test.txt", 40, 20).await?.unwrap(); - assert_eq!(result, &data[40..50]); - - Ok(()) - } - - #[tokio::test] - async fn test_pread_nonexistent_file() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let result = fs.pread("/nonexistent.txt", 0, 10).await?; - assert!(result.is_none()); - - Ok(()) - } - - #[tokio::test] - async fn test_pread_across_chunks() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let chunk_size = fs.chunk_size(); - - // Create data spanning multiple chunks - let data: Vec = (0..(chunk_size * 3)).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - - // Read across chunk boundary - let start = chunk_size - 10; - let result = fs.pread("/test.txt", start as u64, 20).await?.unwrap(); - assert_eq!(result, &data[start..start + 20]); - - // Read spanning multiple chunks - let start = chunk_size / 2; - let size = chunk_size * 2; - let result = fs - .pread("/test.txt", start as u64, size as u64) - .await? - .unwrap(); - assert_eq!(result, &data[start..start + size]); - - Ok(()) - } - - #[tokio::test] - async fn test_pwrite_basic() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Write initial data - let data: Vec = vec![0; 100]; - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - - // Overwrite in the middle - fs.pwrite("/test.txt", 50, &[1, 2, 3, 4, 5]).await?; - - let result = fs.read_file("/test.txt").await?.unwrap(); - assert_eq!(result.len(), 100); - assert_eq!(&result[50..55], &[1, 2, 3, 4, 5]); - assert_eq!(&result[0..50], &vec![0u8; 50][..]); - assert_eq!(&result[55..100], &vec![0u8; 45][..]); - - Ok(()) - } - - #[tokio::test] - async fn test_pwrite_extend_file() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Write initial data - let data: Vec = vec![1; 50]; - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - - // Write past EOF - should extend with zeros - fs.pwrite("/test.txt", 100, &[2, 2, 2, 2, 2]).await?; - - let result = fs.read_file("/test.txt").await?.unwrap(); - assert_eq!(result.len(), 105); - assert_eq!(&result[0..50], &vec![1u8; 50][..]); - assert_eq!(&result[50..100], &vec![0u8; 50][..]); - assert_eq!(&result[100..105], &[2, 2, 2, 2, 2]); - - Ok(()) - } - - #[tokio::test] - async fn test_pwrite_creates_file() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // pwrite to a non-existent file should create it - fs.pwrite("/new.txt", 0, &[1, 2, 3]).await?; - - let result = fs.read_file("/new.txt").await?.unwrap(); - assert_eq!(result, &[1, 2, 3]); - - Ok(()) - } - - #[tokio::test] - async fn test_pwrite_across_chunks() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let chunk_size = fs.chunk_size(); - - // Create initial data spanning multiple chunks - let data: Vec = vec![0; chunk_size * 3]; - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - - // Write across chunk boundary - let write_data: Vec = (0..20).collect(); - let start = chunk_size - 10; - fs.pwrite("/test.txt", start as u64, &write_data).await?; - - let result = fs.read_file("/test.txt").await?.unwrap(); - assert_eq!(&result[start..start + 20], &write_data[..]); - - // Verify surrounding data is unchanged - assert_eq!(&result[0..start], &vec![0u8; start][..]); - assert_eq!( - &result[start + 20..], - &vec![0u8; chunk_size * 3 - start - 20][..] - ); - - Ok(()) - } - - #[tokio::test] - async fn test_pread_pwrite_roundtrip() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let chunk_size = fs.chunk_size(); - - // Create a file - let initial: Vec = (0..(chunk_size * 2)).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &initial).await?; - - // Write some data at various offsets - let patches = vec![ - (0u64, vec![0xAAu8; 10]), - (chunk_size as u64 - 5, vec![0xBB; 10]), - (chunk_size as u64 * 2 - 1, vec![0xCC; 1]), - ]; - - for (offset, data) in &patches { - fs.pwrite("/test.txt", *offset, data).await?; - } - - // Verify with pread - for (offset, expected) in &patches { - let result = fs - .pread("/test.txt", *offset, expected.len() as u64) - .await? - .unwrap(); - assert_eq!(&result, expected); - } - - Ok(()) - } - - #[tokio::test] - async fn test_pwrite_ranges_preserves_order_and_inline_storage() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let (_, file) = fs - .create_file("/batch-inline.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite_ranges(vec![ - WriteRange { - offset: 0, - data: b"abcdef".to_vec(), - }, - WriteRange { - offset: 2, - data: b"ZZ".to_vec(), - }, - WriteRange { - offset: 6, - data: b"!".to_vec(), - }, - ]) - .await?; - - let ino = fs.resolve_path("/batch-inline.txt").await?.unwrap(); - assert_eq!(file.pread(0, 16).await?, b"abZZef!"); - assert_eq!(fs.get_chunk_count(ino).await?, 0); - assert_eq!( - fs.get_storage_state(ino).await?, - (STORAGE_INLINE, Some(b"abZZef!".to_vec())) - ); - - Ok(()) - } - - #[tokio::test] - async fn test_pwrite_ranges_disjoint_inplace_writes_stay_inline() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let initial: Vec = (0..128).collect(); - let (_, file) = fs - .create_file("/batch-inplace.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, &initial).await?; - - file.pwrite_ranges(vec![ - WriteRange { - offset: 8, - data: b"ABCD".to_vec(), - }, - WriteRange { - offset: 64, - data: b"WXYZ".to_vec(), - }, - ]) - .await?; - - let mut expected = initial; - expected[8..12].copy_from_slice(b"ABCD"); - expected[64..68].copy_from_slice(b"WXYZ"); - - let ino = fs.resolve_path("/batch-inplace.bin").await?.unwrap(); - assert_eq!(file.pread(0, expected.len() as u64).await?, expected); - assert_eq!(fs.get_chunk_count(ino).await?, 0); - assert_eq!(fs.get_storage_state(ino).await?.0, STORAGE_INLINE); - - Ok(()) - } - - #[tokio::test] - async fn test_pwrite_ranges_sparse_write_transitions_to_chunked() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let (_, file) = fs - .create_file("/batch-sparse.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite_ranges(vec![ - WriteRange { - offset: 0, - data: b"head".to_vec(), - }, - WriteRange { - offset: fs.chunk_size() as u64 + 4, - data: b"tail".to_vec(), - }, - ]) - .await?; - - let ino = fs.resolve_path("/batch-sparse.bin").await?.unwrap(); - assert_eq!(fs.get_storage_state(ino).await?, (STORAGE_CHUNKED, None)); - assert_eq!(fs.get_chunk_count(ino).await?, 2); - - let mut expected = b"head".to_vec(); - expected.resize(fs.chunk_size() + 4, 0); - expected.extend_from_slice(b"tail"); - assert_eq!(file.pread(0, expected.len() as u64).await?, expected); - - Ok(()) - } - - #[tokio::test] - async fn test_pwrite_ranges_batched_drains_explicitly() -> Result<()> { - std::env::set_var(WRITE_BATCHER_ENABLE_ENV, "1"); - std::env::set_var(WRITE_BATCHER_MS_ENV, "60000"); - std::env::set_var(WRITE_BATCHER_BYTES_ENV, "1048576"); - - let (fs, _dir) = create_test_fs().await?; - let (stats, file) = fs - .create_file("/batched.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - - file.pwrite_ranges_batched(vec![ - WriteRange { - offset: 0, - data: b"hello".to_vec(), - }, - WriteRange { - offset: 5, - data: b" world".to_vec(), - }, - ]) - .await?; - - let flushed_stats = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); - assert_eq!( - flushed_stats.size, 11, - "metadata reads should drain pending batched writes before reporting size" - ); - - file.drain_writes().await?; - assert_eq!(file.pread(0, 32).await?, b"hello world"); - - Ok(()) - } - - #[tokio::test] - async fn test_setattr_after_batched_write_preserves_explicit_times() -> Result<()> { - std::env::set_var(WRITE_BATCHER_ENABLE_ENV, "1"); - std::env::set_var(WRITE_BATCHER_MS_ENV, "60000"); - std::env::set_var(WRITE_BATCHER_BYTES_ENV, "1048576"); - - let (fs, _dir) = create_test_fs().await?; - let (stats, file) = fs - .create_file("/setattr-after-write.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - - // Buffered write stays in the overlay (long timer, no drain). - file.pwrite_ranges_batched(vec![WriteRange { - offset: 0, - data: b"deferred body".to_vec(), - }]) - .await?; - - // Explicit setattr (the kernel's writeback mtime update) lands while - // the data is still pending. No drain happens here by default. - let explicit_secs = 1_234_567_890; - let explicit_nsec = 42; - FileSystem::utimens( - &fs, - stats.ino, - TimeChange::Omit, - TimeChange::Set(explicit_secs, explicit_nsec as u32), - ) - .await?; - - // The deferred commit must NOT re-stamp mtime/ctime over the explicit - // value the setattr just wrote. - file.drain_writes().await?; - - let after = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); - assert_eq!( - after.mtime, explicit_secs, - "explicit mtime must survive the deferred data commit" - ); - assert_eq!( - after.mtime_nsec, explicit_nsec, - "explicit mtime_nsec must survive the deferred data commit" - ); - assert_eq!(after.size, 13); - assert_eq!(file.pread(0, 32).await?, b"deferred body"); - - Ok(()) - } - - #[tokio::test] - async fn test_write_after_setattr_restamps_times_on_commit() -> Result<()> { - std::env::set_var(WRITE_BATCHER_ENABLE_ENV, "1"); - std::env::set_var(WRITE_BATCHER_MS_ENV, "60000"); - std::env::set_var(WRITE_BATCHER_BYTES_ENV, "1048576"); - - let (fs, _dir) = create_test_fs().await?; - let (stats, file) = fs - .create_file("/write-after-setattr.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - - file.pwrite_ranges_batched(vec![WriteRange { - offset: 0, - data: b"first".to_vec(), - }]) - .await?; - - let stale_secs = 1_111_111_111; - FileSystem::utimens( - &fs, - stats.ino, - TimeChange::Omit, - TimeChange::Set(stale_secs, 0), - ) - .await?; - - // A write AFTER the setattr means the file changed again: the commit - // must stamp fresh mtime/ctime, not preserve the stale explicit value. - file.pwrite_ranges_batched(vec![WriteRange { - offset: 5, - data: b" second".to_vec(), - }]) - .await?; - - file.drain_writes().await?; - - let after = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); - assert!( - after.mtime > stale_secs, - "a write after the explicit setattr must bump mtime again (got {}, explicit was {})", - after.mtime, - stale_secs - ); - assert_eq!(file.pread(0, 32).await?, b"first second"); - - Ok(()) - } - - #[tokio::test] - async fn test_utimens_with_pending_writes_is_visible_and_committed_with_data() -> Result<()> { - std::env::set_var(WRITE_BATCHER_ENABLE_ENV, "1"); - std::env::set_var(WRITE_BATCHER_MS_ENV, "60000"); - std::env::set_var(WRITE_BATCHER_BYTES_ENV, "1048576"); - - let (fs, _dir) = create_test_fs().await?; - let (stats, file) = fs - .create_file("/stash-times.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - - // Buffered write stays in the overlay (long timer, no drain). - file.pwrite_ranges_batched(vec![WriteRange { - offset: 0, - data: b"stash body".to_vec(), - }]) - .await?; - - // The explicit setattr is stashed in the pending entry instead of - // paying its own SQLite transaction. - let explicit_secs = 1_999_999_999; - let explicit_nsec: u32 = 7; - FileSystem::utimens( - &fs, - stats.ino, - TimeChange::Set(11, 13), - TimeChange::Set(explicit_secs, explicit_nsec), - ) - .await?; - - // Visible immediately, before any drain commits the row UPDATE. - let before = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); - assert_eq!( - before.mtime, explicit_secs, - "stashed mtime must be visible before the drain commits it" - ); - assert_eq!(before.mtime_nsec, explicit_nsec); - assert_eq!(before.atime, 11); - assert_eq!(before.atime_nsec, 13); - assert_eq!(before.size, 10, "pending data size must still be merged"); - - // The drain commits the data and the stashed times in one transaction. - file.drain_writes().await?; - - let after = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); - assert_eq!( - after.mtime, explicit_secs, - "explicit mtime must survive the deferred data commit" - ); - assert_eq!(after.mtime_nsec, explicit_nsec); - assert_eq!(after.atime, 11); - assert_eq!(after.atime_nsec, 13); - assert_eq!(after.size, 10); - assert_eq!(file.pread(0, 32).await?, b"stash body"); - - Ok(()) - } - - #[tokio::test] - async fn test_write_after_stashed_utimens_restamps_mtime_keeps_atime() -> Result<()> { - std::env::set_var(WRITE_BATCHER_ENABLE_ENV, "1"); - std::env::set_var(WRITE_BATCHER_MS_ENV, "60000"); - std::env::set_var(WRITE_BATCHER_BYTES_ENV, "1048576"); - - let (fs, _dir) = create_test_fs().await?; - let (stats, file) = fs - .create_file("/stash-then-write.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - - file.pwrite_ranges_batched(vec![WriteRange { - offset: 0, - data: b"first".to_vec(), - }]) - .await?; - - let stale_secs = 1_222_222_222; - FileSystem::utimens( - &fs, - stats.ino, - TimeChange::Set(33, 44), - TimeChange::Set(stale_secs, 0), - ) - .await?; - - // A write AFTER the stashed setattr means the file changed again: the - // commit must stamp fresh mtime/ctime. The explicitly-set atime is not - // affected by writes and must survive. - file.pwrite_ranges_batched(vec![WriteRange { - offset: 5, - data: b" second".to_vec(), - }]) - .await?; - - file.drain_writes().await?; - - let after = FileSystem::getattr(&fs, stats.ino).await?.unwrap(); - assert!( - after.mtime > stale_secs, - "a write after the stashed setattr must bump mtime again (got {}, explicit was {})", - after.mtime, - stale_secs - ); - assert_eq!(after.atime, 33, "explicit atime must survive a later write"); - assert_eq!(after.atime_nsec, 44); - assert_eq!(file.pread(0, 32).await?, b"first second"); - - Ok(()) - } - - // Build a batcher with an explicit config so the test is independent of the - // process-global AGENTFS_BATCH_* env vars (which other tests mutate - // concurrently). Reuses `fs`'s pool/attr cache so commits hit real inodes. - fn test_batcher( - fs: &AgentFS, - batch_ms_secs: u64, - batch_bytes: usize, - batch_global_bytes: usize, - ) -> Arc { - Arc::new(AgentFSWriteBatcher { - pool: fs.pool.clone(), - chunk_size: fs.chunk_size, - inline_threshold: fs.inline_threshold, - attr_cache: fs.attr_cache.clone(), - batch_ms: Duration::from_secs(batch_ms_secs), - batch_bytes, - batch_global_bytes, - txn_max_inodes: DEFAULT_WRITE_BATCH_TXN_INODES, - txn_max_bytes: DEFAULT_WRITE_BATCH_TXN_BYTES, - state: RwLock::new(AgentFSWriteBatcherState::default()), - commit_lock: AsyncMutex::new(()), - }) - } - - #[tokio::test] - async fn test_batcher_global_cap_triggers_full_drain_and_tracks_total() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (sa, _fa) = fs.create_file("/a.bin", DEFAULT_FILE_MODE, 0, 0).await?; - let (sb, _fb) = fs.create_file("/b.bin", DEFAULT_FILE_MODE, 0, 0).await?; - - // 10-minute timer and huge per-inode trigger so the ONLY drain path is - // the 64-byte global cross-inode cap. - let batcher = test_batcher(&fs, 600, 1 << 20, 64); - - // Write below the cap to inode A: stays pending. - batcher - .enqueue( - sa.ino, - vec![WriteRange { - offset: 0, - data: vec![b'x'; 50], - }], - ) - .await?; - assert_eq!( - batcher.state.read().total_pending_bytes, - 50, - "write below the global cap must remain in the overlay" - ); - - // Truncating into the pending range shrinks the tracked total. - batcher.truncate_pending(sa.ino, 20); - assert_eq!( - batcher.state.read().total_pending_bytes, - 20, - "truncate_pending must shrink the running total to the kept prefix" - ); - - // Write to inode B crosses the cap (20 + 50 >= 64): a full batched drain - // commits every pending inode and resets the running total to zero. - batcher - .enqueue( - sb.ino, - vec![WriteRange { - offset: 0, - data: vec![b'y'; 50], - }], - ) - .await?; - assert_eq!( - batcher.state.read().total_pending_bytes, - 0, - "crossing the global cap must drain all pending inodes" - ); - - // Committed data is intact and reflects the truncate. - assert_eq!(fs.read_file("/a.bin").await?.unwrap(), vec![b'x'; 20]); - assert_eq!(fs.read_file("/b.bin").await?.unwrap(), vec![b'y'; 50]); - Ok(()) - } - - #[tokio::test] - async fn test_batcher_discard_pending_updates_total() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (sa, _fa) = fs.create_file("/c.bin", DEFAULT_FILE_MODE, 0, 0).await?; - - // No timer/bytes/global drain: writes accumulate so we can observe the - // total before discarding. - let batcher = test_batcher(&fs, 600, 1 << 20, 1 << 30); - batcher - .enqueue( - sa.ino, - vec![WriteRange { - offset: 0, - data: vec![b'z'; 100], - }], - ) - .await?; - assert_eq!(batcher.state.read().total_pending_bytes, 100); - - batcher.discard_pending(sa.ino); - assert_eq!( - batcher.state.read().total_pending_bytes, - 0, - "discard_pending must subtract the discarded inode's bytes" - ); - Ok(()) - } - - // ───────────────────────────────────────────────────────────── - // Truncate Tests - // ───────────────────────────────────────────────────────────── - - #[tokio::test] - async fn test_truncate_to_zero() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create a file with some data - let data: Vec = (0..100).collect(); - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - - // Truncate to zero - fs.truncate("/test.txt", 0).await?; - - // Verify file is empty - let result = fs.read_file("/test.txt").await?.unwrap(); - assert!(result.is_empty()); - - // Verify stat shows size 0 - let stats = fs.stat("/test.txt").await?.unwrap(); - assert_eq!(stats.size, 0); - - Ok(()) - } - - #[tokio::test] - async fn test_truncate_smaller_within_chunk() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create a file smaller than chunk size - let data: Vec = (0..100).collect(); - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - - // Truncate to 50 bytes - fs.truncate("/test.txt", 50).await?; - - // Verify data is truncated correctly - let result = fs.read_file("/test.txt").await?.unwrap(); - assert_eq!(result.len(), 50); - assert_eq!(result, &data[..50]); - - Ok(()) - } - - #[tokio::test] - async fn test_truncate_across_chunk_boundary() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let chunk_size = fs.chunk_size(); - - // Create a file spanning multiple chunks - let data: Vec = (0..(chunk_size * 3)).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - - // Truncate to middle of second chunk - let new_size = chunk_size + chunk_size / 2; - fs.truncate("/test.txt", new_size as u64).await?; - - // Verify data - let result = fs.read_file("/test.txt").await?.unwrap(); - assert_eq!(result.len(), new_size); - assert_eq!(result, &data[..new_size]); - - Ok(()) - } - - #[tokio::test] - async fn test_truncate_extend_file() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create a small file - let data: Vec = (0..50).collect(); - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - - // Extend to 100 bytes - fs.truncate("/test.txt", 100).await?; - - // Verify size increased - let stats = fs.stat("/test.txt").await?.unwrap(); - assert_eq!(stats.size, 100); - - // Original data should be preserved, rest should be zeros (sparse) - let result = fs.read_file("/test.txt").await?.unwrap(); - assert_eq!(result.len(), 100); - assert_eq!(&result[..50], &data[..]); - - Ok(()) - } - - #[tokio::test] - async fn test_truncate_nonexistent_file() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Truncate non-existent file should fail - let result = fs.truncate("/nonexistent.txt", 100).await; - assert!(result.is_err()); - - Ok(()) - } - - #[tokio::test] - async fn test_truncate_at_chunk_boundary() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let chunk_size = fs.chunk_size(); - - // Create a file spanning multiple chunks - let data: Vec = (0..(chunk_size * 3)).map(|i| (i % 256) as u8).collect(); - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &data).await?; - - // Truncate exactly at chunk boundary - fs.truncate("/test.txt", chunk_size as u64).await?; - - // Verify - let result = fs.read_file("/test.txt").await?.unwrap(); - assert_eq!(result.len(), chunk_size); - assert_eq!(result, &data[..chunk_size]); - - Ok(()) - } - - // ───────────────────────────────────────────────────────────── - // Rename Tests - // ───────────────────────────────────────────────────────────── - - #[tokio::test] - async fn test_rename_file_same_directory() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create a file - let data = b"hello world"; - let (_, file) = fs.create_file("/old.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, data).await?; - - // Rename it - fs.rename("/old.txt", "/new.txt").await?; - - // Old path should not exist - assert!(fs.stat("/old.txt").await?.is_none()); - - // New path should exist with same data - let result = fs.read_file("/new.txt").await?.unwrap(); - assert_eq!(result, data); - - Ok(()) - } - - #[tokio::test] - async fn test_rename_file_to_different_directory() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create directory and file - fs.mkdir("/subdir", 0, 0).await?; - let data = b"test data"; - let (_, file) = fs.create_file("/file.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, data).await?; - - // Move file to subdirectory - fs.rename("/file.txt", "/subdir/file.txt").await?; - - // Old path should not exist - assert!(fs.stat("/file.txt").await?.is_none()); - - // New path should exist - let result = fs.read_file("/subdir/file.txt").await?.unwrap(); - assert_eq!(result, data); - - Ok(()) - } - - #[tokio::test] - async fn test_rename_overwrite_existing_file() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create two files - let (_, file) = fs.create_file("/src.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, b"source").await?; - let (_, file) = fs.create_file("/dst.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, b"destination").await?; - - // Rename src to dst (overwrites dst) - fs.rename("/src.txt", "/dst.txt").await?; - - // Only dst should exist with src's content - assert!(fs.stat("/src.txt").await?.is_none()); - let result = fs.read_file("/dst.txt").await?.unwrap(); - assert_eq!(result, b"source"); - - Ok(()) - } - - #[tokio::test] - async fn test_rename_directory() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create directory with a file inside - fs.mkdir("/olddir", 0, 0).await?; - let (_, file) = fs - .create_file("/olddir/file.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, b"content").await?; - - // Rename directory - fs.rename("/olddir", "/newdir").await?; - - // Old path should not exist - assert!(fs.stat("/olddir").await?.is_none()); - - // New path should exist and contain the file - assert!(fs.stat("/newdir").await?.is_some()); - let result = fs.read_file("/newdir/file.txt").await?.unwrap(); - assert_eq!(result, b"content"); - - Ok(()) - } - - #[tokio::test] - async fn test_rename_directory_into_own_subtree_fails() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create nested directories - fs.mkdir("/parent", 0, 0).await?; - fs.mkdir("/parent/child", 0, 0).await?; - - // Try to rename parent into its child - should fail - let result = fs.rename("/parent", "/parent/child/parent").await; - assert!(result.is_err()); - - // Original structure should be intact - assert!(fs.stat("/parent").await?.is_some()); - assert!(fs.stat("/parent/child").await?.is_some()); - - Ok(()) - } - - #[tokio::test] - async fn test_rename_root_fails() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Try to rename root - should fail - let result = fs.rename("/", "/newroot").await; - assert!(result.is_err()); - - Ok(()) - } - - #[tokio::test] - async fn test_rename_to_root_fails() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - let (_, file) = fs.create_file("/file.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, b"data").await?; - - // Try to rename to root - should fail - let result = fs.rename("/file.txt", "/").await; - assert!(result.is_err()); - - Ok(()) - } - - #[tokio::test] - async fn test_rename_nonexistent_source_fails() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Try to rename non-existent file - let result = fs.rename("/nonexistent.txt", "/new.txt").await; - assert!(result.is_err()); - - Ok(()) - } - - #[tokio::test] - async fn test_rename_overwrite_nonempty_directory_fails() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create source directory and target directory with content - fs.mkdir("/src", 0, 0).await?; - fs.mkdir("/dst", 0, 0).await?; - let (_, file) = fs - .create_file("/dst/file.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, b"content").await?; - - // Try to rename src to dst (dst is not empty) - should fail - let result = fs.rename("/src", "/dst").await; - assert!(result.is_err()); - - // Both directories should still exist - assert!(fs.stat("/src").await?.is_some()); - assert!(fs.stat("/dst").await?.is_some()); - assert!(fs.stat("/dst/file.txt").await?.is_some()); - - Ok(()) - } - - #[tokio::test] - async fn test_rename_file_to_directory_fails() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create a file and an empty directory - let (_, file) = fs.create_file("/file.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, b"data").await?; - fs.mkdir("/dir", 0, 0).await?; - - // Try to rename file over directory - should fail - let result = fs.rename("/file.txt", "/dir").await; - assert!(result.is_err()); - - Ok(()) - } - - #[tokio::test] - async fn test_rename_directory_to_file_fails() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create a directory and a file - fs.mkdir("/dir", 0, 0).await?; - let (_, file) = fs.create_file("/file.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, b"data").await?; - - // Try to rename directory over file - should fail - let result = fs.rename("/dir", "/file.txt").await; - assert!(result.is_err()); - - Ok(()) - } - - #[tokio::test] - async fn test_rename_updates_ctime() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create a file - let (_, file) = fs.create_file("/old.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, b"data").await?; - let stats_before = fs.stat("/old.txt").await?.unwrap(); - - // Small delay to ensure time changes - tokio::time::sleep(std::time::Duration::from_millis(10)).await; - - // Rename it - fs.rename("/old.txt", "/new.txt").await?; - - // ctime should be updated - let stats_after = fs.stat("/new.txt").await?.unwrap(); - assert!(stats_after.ctime >= stats_before.ctime); - - Ok(()) - } - - #[tokio::test] - async fn test_chmod_regular_file() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create a file with default permissions - let (_, file) = fs.create_file("/test.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, b"content").await?; - - let stats = fs.stat("/test.txt").await?.unwrap(); - let ino = stats.ino; - assert_eq!( - stats.mode & 0o7777, - 0o644, - "Default file mode should be 0o644" - ); - - // Change to executable - fs.chmod(ino, 0o755).await?; - - let stats = fs.stat("/test.txt").await?.unwrap(); - assert_eq!( - stats.mode & 0o7777, - 0o755, - "Mode should be 0o755 after chmod" - ); - assert!(stats.is_file(), "Should still be a regular file"); - - // Change to read-only - fs.chmod(ino, 0o444).await?; - - let stats = fs.stat("/test.txt").await?.unwrap(); - assert_eq!( - stats.mode & 0o7777, - 0o444, - "Mode should be 0o444 after chmod" - ); - - Ok(()) - } - - #[tokio::test] - async fn test_chmod_preserves_file_type() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create a regular file - let (file_stats, file) = fs.create_file("/file.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, b"content").await?; - fs.chmod(file_stats.ino, 0o755).await?; - let stats = fs.stat("/file.txt").await?.unwrap(); - assert!(stats.is_file(), "Should remain a regular file after chmod"); - - // Create a directory - fs.mkdir("/dir", 0, 0).await?; - let dir_stats = fs.stat("/dir").await?.unwrap(); - fs.chmod(dir_stats.ino, 0o700).await?; - let stats = fs.stat("/dir").await?.unwrap(); - assert!( - stats.is_directory(), - "Should remain a directory after chmod" - ); - assert_eq!(stats.mode & 0o7777, 0o700, "Directory mode should be 0o700"); - - Ok(()) - } - - #[tokio::test] - async fn test_chmod_nonexistent_fails() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Use a non-existent inode - let result = fs.chmod(999999, 0o755).await; - assert!(result.is_err(), "chmod on nonexistent inode should fail"); - - Ok(()) - } - - #[tokio::test] - async fn test_chmod_symlink() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - - // Create target and symlink - let (_, file) = fs - .create_file("/target.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, b"content").await?; - fs.symlink("/target.txt", "/link.txt", 0, 0).await?; - let link_stats = fs.lstat("/link.txt").await?.unwrap(); - - // chmod the symlink (should work on the symlink inode) - fs.chmod(link_stats.ino, 0o755).await?; - - let stats = fs.lstat("/link.txt").await?.unwrap(); - assert!(stats.is_symlink(), "Should still be a symlink"); - - Ok(()) - } - - // ==================== Tier Four: Overlay Read-After-Write ==================== - // - // These exercise the Tier 4 invariant that `pread` / `getattr` / - // `truncate` reflect pending batched writes BEFORE the SQLite drain - // commits them — i.e. the per-fd write-then-read story works without - // forcing a synchronous SQLite transaction on every read. - - #[tokio::test] - async fn pread_after_uncommitted_pwrite_sees_pending() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (_, file) = fs - .create_file("/overlay.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, b"hello world").await?; - // No fsync — Tier 4 says the same fd must see its own writes via - // the in-memory overlay, regardless of whether SQLite has them yet. - assert_eq!(file.pread(0, 11).await?, b"hello world"); - assert_eq!(file.pread(6, 5).await?, b"world"); - Ok(()) - } - - #[tokio::test] - async fn pread_after_uncommitted_pwrite_partial_overlap() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (_, file) = fs.create_file("/over.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, b"AAAAAAAAAA").await?; - file.fsync().await?; - file.pwrite(4, b"BBB").await?; - // Read spans SQLite-resident (A) and pending (B) regions. - assert_eq!(file.pread(2, 6).await?, b"AABBBA"); - Ok(()) - } - - #[tokio::test] - async fn pread_in_unwritten_region_returns_sqlite() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (_, file) = fs.create_file("/hole.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(0, &[0xCDu8; 64]).await?; - file.fsync().await?; - file.pwrite(80, b"tail").await?; - // Read [16, 32) — entirely SQLite, no pending overlap. - assert_eq!(file.pread(16, 16).await?, vec![0xCDu8; 16]); - Ok(()) - } - - #[tokio::test] - async fn truncate_drops_pending_beyond_new_size() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (_, file) = fs - .create_file("/trunc.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, b"abcdef").await?; - file.truncate(3).await?; - assert_eq!(file.pread(0, 16).await?, b"abc"); - let attrs = FileSystem::getattr(&fs, fs.resolve_path("/trunc.txt").await?.unwrap()) - .await? - .unwrap(); - assert_eq!(attrs.size, 3); - Ok(()) - } - - #[tokio::test] - async fn truncate_clips_range_spanning_boundary() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (_, file) = fs.create_file("/clip.txt", DEFAULT_FILE_MODE, 0, 0).await?; - file.pwrite(2, b"PPPPPP").await?; - // pending occupies [2, 8). Truncate to 5 should keep [2, 5). - file.truncate(5).await?; - assert_eq!(file.pread(0, 16).await?, vec![0, 0, b'P', b'P', b'P']); - Ok(()) - } - - #[tokio::test] - async fn getattr_reflects_pending_size_growth() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (created, file) = fs.create_file("/grow.txt", DEFAULT_FILE_MODE, 0, 0).await?; - let pre = FileSystem::getattr(&fs, created.ino).await?.unwrap(); - assert_eq!(pre.size, 0); - file.pwrite(0, b"abcdefghij").await?; - let post = FileSystem::getattr(&fs, created.ino).await?.unwrap(); - assert_eq!(post.size, 10); - Ok(()) - } - - #[tokio::test] - async fn concurrent_writers_overlay_merge() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (_, fh_a) = fs - .create_file("/multi.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - let ino = fs.resolve_path("/multi.txt").await?.unwrap(); - let fh_b = fs.open("/multi.txt").await?; - fh_a.pwrite(0, b"AAAA").await?; - fh_b.pwrite(4, b"BBBB").await?; - // Either fd should see both writes merged via the overlay. - assert_eq!(fh_a.pread(0, 8).await?, b"AAAABBBB"); - assert_eq!(fh_b.pread(0, 8).await?, b"AAAABBBB"); - // And getattr reflects the combined size. - let attrs = FileSystem::getattr(&fs, ino).await?.unwrap(); - assert_eq!(attrs.size, 8); - Ok(()) - } - - #[tokio::test] - async fn unlink_during_pending_writes_no_orphan() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (created, file) = fs - .create_file("/doomed.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, b"these bytes never reach SQLite").await?; - // Unlink before any drain. Tier 4 hooks discard_pending here. - fs.remove("/doomed.txt").await?; - // Force a batched drain. If pending was not discarded, we'd hit - // NotFound when commit_inode_ranges looks up fs_inode for the - // unlinked ino. The drain must therefore succeed. - fs.drain_all().await?; - // And the row truly is gone. - assert!(fs.stat("/doomed.txt").await?.is_none()); - let conn = fs.pool.get_connection().await?; - let count: i64 = { - let mut rows = conn - .query("SELECT COUNT(*) FROM fs_data WHERE ino = ?", (created.ino,)) - .await?; - rows.next() - .await? - .and_then(|r| r.get_value(0).ok().and_then(|v| v.as_integer().copied())) - .unwrap_or(-1) - }; - assert_eq!(count, 0, "no orphan fs_data rows for unlinked ino"); - Ok(()) - } - - #[tokio::test] - async fn fsync_drains_overlay_to_sqlite() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (created, file) = fs - .create_file("/durable.txt", DEFAULT_FILE_MODE, 0, 0) - .await?; - file.pwrite(0, b"persist me").await?; - // Before fsync, the bytes are in the overlay; get_chunk_count drains - // them as part of the test helper (Tier 4 sync helper change). - // After fsync, the chunk count should be observable without any - // helper drain prelude. - file.fsync().await?; - let conn = fs.pool.get_connection().await?; - let count: i64 = { - let mut rows = conn - .query("SELECT size FROM fs_inode WHERE ino = ?", (created.ino,)) - .await?; - rows.next() - .await? - .and_then(|r| r.get_value(0).ok().and_then(|v| v.as_integer().copied())) - .unwrap_or(-1) - }; - assert_eq!(count, 10, "fsync committed pending size to fs_inode"); - Ok(()) - } - - /// Spec acceptance criterion for Tier 4: - /// "`agentfs_batcher_drains_explicit / agentfs_batcher_enqueues` ratio - /// drops to <0.2 (vs ~1.0 today) — confirms read path no longer triggers - /// Explicit drains." - /// - /// We simulate a read-after-write workload (write, read, write, read, ...) - /// and assert that the SDK does NOT call drain_inode_writes - /// (Explicit drain) on every read. With Tier 4 the read path peeks the - /// overlay; with Tier 3 each read forces drain → ratio ≈ 1.0. - #[tokio::test] - async fn tier_four_drains_explicit_to_enqueues_ratio_under_0_2() -> Result<()> { - let (fs, _dir) = create_test_fs().await?; - let (_, file) = fs - .create_file("/ratio.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - - let pre = crate::profiling::snapshot(); - let pre_enq = pre.agentfs_batcher_enqueues; - let pre_explicit = pre.agentfs_batcher_drains_explicit; - - // 200 write-then-read cycles, no intervening fsync. Tier 3 would - // drain Explicit on every read; Tier 4 must not. - for i in 0..200u64 { - file.pwrite(i * 4, b"abcd").await?; - let _ = file.pread(i * 4, 4).await?; - } - - let post = crate::profiling::snapshot(); - let enq = post.agentfs_batcher_enqueues - pre_enq; - let explicit = post.agentfs_batcher_drains_explicit - pre_explicit; - assert!(enq >= 200, "expected ≥200 enqueues, got {enq}"); - let ratio = explicit as f64 / enq.max(1) as f64; - assert!( - ratio < 0.2, - "Tier 4 acceptance: drains_explicit/enqueues should be <0.2; \ - got {explicit}/{enq} = {ratio:.3}" - ); - Ok(()) - } - - /// Spec escape-hatch verification: with the overlay disabled, the SDK - /// reverts to Tier 3 drain-on-write semantics. `pwrite` should commit - /// straight to SQLite (no batcher enqueue), and `pread` should see the - /// value without ever consulting `peek_pending`. This locks in the kill - /// switch the spec's risk table called for. - #[tokio::test] - async fn overlay_reads_flag_off_falls_back_to_drain_on_write() -> Result<()> { - let (mut fs, _dir) = create_test_fs().await?; - fs.overlay_reads = false; - let (_, file) = fs - .create_file("/escape.bin", DEFAULT_FILE_MODE, 0, 0) - .await?; - - file.pwrite(0, b"hello world").await?; - // Per-inode check rather than the global enqueue counter: parallel - // tests share the profiling globals, so counter deltas race. - let escape_ino = fs.resolve_path("/escape.bin").await?.unwrap(); - if let Some(batcher) = &fs.write_batcher { - assert!( - !batcher.has_pending(escape_ino), - "with overlay_reads=false, pwrite must not enqueue" - ); - } - let got = file.pread(0, 11).await?; - assert_eq!(&got, b"hello world"); - - // And the file is durably in SQLite without an explicit fsync — - // the Tier 3 contract. - let ino = fs.resolve_path("/escape.bin").await?.unwrap(); - let conn = fs.pool.get_connection().await?; - let size: i64 = { - let mut rows = conn - .query("SELECT size FROM fs_inode WHERE ino = ?", (ino,)) - .await?; - rows.next() - .await? - .and_then(|r| r.get_value(0).ok().and_then(|v| v.as_integer().copied())) - .unwrap_or(-1) - }; - assert_eq!( - size, 11, - "overlay_reads=false → SQLite has full size after pwrite" - ); - Ok(()) - } -} diff --git a/sdk/rust/src/profiling.rs b/sdk/rust/src/profiling.rs deleted file mode 100644 index 9bab94ae..00000000 --- a/sdk/rust/src/profiling.rs +++ /dev/null @@ -1,1967 +0,0 @@ -//! Lightweight env-gated profiling counters for AgentFS hot paths. -//! -//! The public recording helpers are intentionally tiny when profiling is -//! disabled: each call performs one cached environment-gate check and returns. - -use serde::Serialize; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::time::Duration; - -#[cfg(not(test))] -static ENABLED: std::sync::OnceLock = std::sync::OnceLock::new(); -static COUNTERS: ProfileCounters = ProfileCounters::new(); - -/// Snapshot of AgentFS profiling counters. -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] -pub struct ProfileSnapshot { - pub connection_wait_count: u64, - pub connection_wait_nanos: u64, - pub connection_create_count: u64, - pub connection_reuse_count: u64, - pub lookup_count: u64, - pub lookup_delta_count: u64, - pub lookup_base_count: u64, - pub lookup_whiteout_count: u64, - pub getattr_count: u64, - pub readdir_count: u64, - pub readdir_plus_count: u64, - pub path_resolution_count: u64, - pub path_component_count: u64, - pub path_cache_hits: u64, - pub path_cache_misses: u64, - pub negative_lookup_count: u64, - pub negative_cache_hits: u64, - pub negative_cache_misses: u64, - pub negative_cache_invalidations: u64, - pub attr_cache_hits: u64, - pub attr_cache_misses: u64, - pub dentry_cache_hits: u64, - pub dentry_cache_misses: u64, - pub chunk_read_queries: u64, - pub chunk_read_chunks: u64, - pub chunk_write_chunks: u64, - pub agentfs_batcher_enqueues: u64, - pub agentfs_batcher_drains_timer: u64, - pub agentfs_batcher_drains_bytes: u64, - pub agentfs_batcher_drains_explicit: u64, - pub agentfs_batcher_pending_max_bytes: u64, - pub agentfs_batcher_coalesced_ranges: u64, - pub agentfs_batcher_commit_latency_ns_total: u64, - pub agentfs_batcher_commit_txns: u64, - pub agentfs_batcher_txn_inodes_total: u64, - pub agentfs_batcher_txn_inodes_max: u64, - pub wal_checkpoint_count: u64, - pub wal_checkpoint_nanos: u64, - pub fuse_callback_count: u64, - pub fuse_lookup_count: u64, - pub fuse_getattr_count: u64, - pub fuse_readdir_count: u64, - pub fuse_readdir_plus_count: u64, - pub fuse_open_count: u64, - pub fuse_uring_requests: u64, - pub fuse_read_count: u64, - pub fuse_release_count: u64, - pub fuse_write_count: u64, - pub fuse_write_bytes: u64, - pub fuse_flush_count: u64, - pub fuse_flush_ranges: u64, - pub fuse_flush_bytes: u64, - pub fuse_noflush_enosys_replies: u64, - pub fuse_pending_tail_drains: u64, - pub fuse_noopen_enosys_replies: u64, - pub fuse_ino_file_resolutions: u64, - pub fuse_ino_file_upgrades: u64, - pub fuse_sync_inval_inode_ok: u64, - pub fuse_sync_inval_inode_err: u64, - pub fuse_sync_inval_entry_ok: u64, - pub fuse_sync_inval_entry_err: u64, - pub fuse_sync_inval_latency_ns_total: u64, - pub fuse_dispatch_wait_count: u64, - pub fuse_dispatch_wait_nanos: u64, - pub fuse_adapter_lock_wait_count: u64, - pub fuse_adapter_lock_wait_nanos: u64, - pub fuse_read_lane_wait_count: u64, - pub fuse_read_lane_wait_nanos: u64, - pub fuse_write_lane_wait_count: u64, - pub fuse_write_lane_wait_nanos: u64, - pub fuse_read_lane_max_concurrent: u64, - pub fuse_exclusive_fallback_count: u64, - pub fuse_workers_configured: u64, - pub fuse_worker_queue_depth_peak: u64, - pub fuse_dispatch_inline_fallback: u64, - pub fuse_dispatch_parallel_tasks: u64, - pub fuse_dispatch_max_concurrent: u64, - pub fuse_readdirplus_auto_requested: u64, - pub fuse_readdirplus_auto_enabled: u64, - pub fuse_readdirplus_do_requested: u64, - pub fuse_readdirplus_do_enabled: u64, - pub fuse_readdirplus_unsupported: u64, - pub fuse_readdirplus_mode: u64, - pub fuse_ttl_entry_ms: u64, - pub fuse_ttl_attr_ms: u64, - pub fuse_ttl_neg_ms: u64, - pub fuse_writeback_cache_enabled: u64, - pub fuse_keepcache_enabled: u64, - pub fuse_keepcache_eligibility_drops: u64, - pub fuse_adapter_entry_hits: u64, - pub fuse_adapter_entry_misses: u64, - pub fuse_adapter_attr_hits: u64, - pub fuse_adapter_attr_misses: u64, - pub fuse_adapter_negative_hits: u64, - pub fuse_adapter_negative_misses: u64, - pub fuse_adapter_inval_inode_notifications: u64, - pub fuse_adapter_inval_entry_notifications: u64, - pub base_fast_open_eligible: u64, - pub base_fast_open_keep_cache: u64, - pub base_fast_open_passthrough_attempted: u64, - pub base_fast_open_passthrough_succeeded: u64, - pub base_fast_open_passthrough_fallback: u64, - pub base_fast_open_rejected: u64, - pub base_fast_inode_invalidations: u64, - pub base_fast_stale_rejections: u64, - /// Per-opcode dispatch latency, flattened as - /// `fuse_op__count` / `fuse_op__nanos` keys (zero slots - /// omitted) so generic counter tooling sees plain integers. Measured - /// around the whole dispatch: parse → handler → reply send. - #[serde(flatten)] - pub fuse_op_latency: std::collections::BTreeMap, -} - -/// Dispatch-level FUSE opcode slots for per-op latency accounting. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FuseOpSlot { - Lookup, - GetAttr, - SetAttr, - Open, - Create, - Read, - Write, - Flush, - Release, - ReadDirPlus, - Forget, - Other, -} - -const FUSE_OP_SLOT_COUNT: usize = 12; -const FUSE_OP_SLOT_NAMES: [&str; FUSE_OP_SLOT_COUNT] = [ - "lookup", - "getattr", - "setattr", - "open", - "create", - "read", - "write", - "flush", - "release", - "readdirplus", - "forget", - "other", -]; - -/// Atomic profiling counters. -#[derive(Debug)] -pub struct ProfileCounters { - fuse_op_counts: [AtomicU64; FUSE_OP_SLOT_COUNT], - fuse_op_nanos: [AtomicU64; FUSE_OP_SLOT_COUNT], - connection_wait_count: AtomicU64, - connection_wait_nanos: AtomicU64, - connection_create_count: AtomicU64, - connection_reuse_count: AtomicU64, - lookup_count: AtomicU64, - lookup_delta_count: AtomicU64, - lookup_base_count: AtomicU64, - lookup_whiteout_count: AtomicU64, - getattr_count: AtomicU64, - readdir_count: AtomicU64, - readdir_plus_count: AtomicU64, - path_resolution_count: AtomicU64, - path_component_count: AtomicU64, - path_cache_hits: AtomicU64, - path_cache_misses: AtomicU64, - negative_lookup_count: AtomicU64, - negative_cache_hits: AtomicU64, - negative_cache_misses: AtomicU64, - negative_cache_invalidations: AtomicU64, - attr_cache_hits: AtomicU64, - attr_cache_misses: AtomicU64, - dentry_cache_hits: AtomicU64, - dentry_cache_misses: AtomicU64, - chunk_read_queries: AtomicU64, - chunk_read_chunks: AtomicU64, - chunk_write_chunks: AtomicU64, - agentfs_batcher_enqueues: AtomicU64, - agentfs_batcher_drains_timer: AtomicU64, - agentfs_batcher_drains_bytes: AtomicU64, - agentfs_batcher_drains_explicit: AtomicU64, - agentfs_batcher_pending_max_bytes: AtomicU64, - agentfs_batcher_coalesced_ranges: AtomicU64, - agentfs_batcher_commit_latency_ns_total: AtomicU64, - agentfs_batcher_commit_txns: AtomicU64, - agentfs_batcher_txn_inodes_total: AtomicU64, - agentfs_batcher_txn_inodes_max: AtomicU64, - wal_checkpoint_count: AtomicU64, - wal_checkpoint_nanos: AtomicU64, - fuse_callback_count: AtomicU64, - fuse_lookup_count: AtomicU64, - fuse_getattr_count: AtomicU64, - fuse_readdir_count: AtomicU64, - fuse_readdir_plus_count: AtomicU64, - fuse_open_count: AtomicU64, - fuse_uring_requests: AtomicU64, - fuse_read_count: AtomicU64, - fuse_release_count: AtomicU64, - fuse_write_count: AtomicU64, - fuse_write_bytes: AtomicU64, - fuse_flush_count: AtomicU64, - fuse_flush_ranges: AtomicU64, - fuse_flush_bytes: AtomicU64, - fuse_noflush_enosys_replies: AtomicU64, - fuse_pending_tail_drains: AtomicU64, - fuse_noopen_enosys_replies: AtomicU64, - fuse_ino_file_resolutions: AtomicU64, - fuse_ino_file_upgrades: AtomicU64, - fuse_sync_inval_inode_ok: AtomicU64, - fuse_sync_inval_inode_err: AtomicU64, - fuse_sync_inval_entry_ok: AtomicU64, - fuse_sync_inval_entry_err: AtomicU64, - fuse_sync_inval_latency_ns_total: AtomicU64, - fuse_dispatch_wait_count: AtomicU64, - fuse_dispatch_wait_nanos: AtomicU64, - fuse_adapter_lock_wait_count: AtomicU64, - fuse_adapter_lock_wait_nanos: AtomicU64, - fuse_read_lane_wait_count: AtomicU64, - fuse_read_lane_wait_nanos: AtomicU64, - fuse_write_lane_wait_count: AtomicU64, - fuse_write_lane_wait_nanos: AtomicU64, - fuse_read_lane_max_concurrent: AtomicU64, - fuse_exclusive_fallback_count: AtomicU64, - fuse_workers_configured: AtomicU64, - fuse_worker_queue_depth_peak: AtomicU64, - fuse_dispatch_inline_fallback: AtomicU64, - fuse_dispatch_parallel_tasks: AtomicU64, - fuse_dispatch_max_concurrent: AtomicU64, - fuse_readdirplus_auto_requested: AtomicU64, - fuse_readdirplus_auto_enabled: AtomicU64, - fuse_readdirplus_do_requested: AtomicU64, - fuse_readdirplus_do_enabled: AtomicU64, - fuse_readdirplus_unsupported: AtomicU64, - fuse_readdirplus_mode: AtomicU64, - fuse_ttl_entry_ms: AtomicU64, - fuse_ttl_attr_ms: AtomicU64, - fuse_ttl_neg_ms: AtomicU64, - fuse_writeback_cache_enabled: AtomicU64, - fuse_keepcache_enabled: AtomicU64, - fuse_keepcache_eligibility_drops: AtomicU64, - fuse_adapter_entry_hits: AtomicU64, - fuse_adapter_entry_misses: AtomicU64, - fuse_adapter_attr_hits: AtomicU64, - fuse_adapter_attr_misses: AtomicU64, - fuse_adapter_negative_hits: AtomicU64, - fuse_adapter_negative_misses: AtomicU64, - fuse_adapter_inval_inode_notifications: AtomicU64, - fuse_adapter_inval_entry_notifications: AtomicU64, - base_fast_open_eligible: AtomicU64, - base_fast_open_keep_cache: AtomicU64, - base_fast_open_passthrough_attempted: AtomicU64, - base_fast_open_passthrough_succeeded: AtomicU64, - base_fast_open_passthrough_fallback: AtomicU64, - base_fast_open_rejected: AtomicU64, - base_fast_inode_invalidations: AtomicU64, - base_fast_stale_rejections: AtomicU64, -} - -impl ProfileCounters { - pub const fn new() -> Self { - Self { - fuse_op_counts: [const { AtomicU64::new(0) }; FUSE_OP_SLOT_COUNT], - fuse_op_nanos: [const { AtomicU64::new(0) }; FUSE_OP_SLOT_COUNT], - connection_wait_count: AtomicU64::new(0), - connection_wait_nanos: AtomicU64::new(0), - connection_create_count: AtomicU64::new(0), - connection_reuse_count: AtomicU64::new(0), - lookup_count: AtomicU64::new(0), - lookup_delta_count: AtomicU64::new(0), - lookup_base_count: AtomicU64::new(0), - lookup_whiteout_count: AtomicU64::new(0), - getattr_count: AtomicU64::new(0), - readdir_count: AtomicU64::new(0), - readdir_plus_count: AtomicU64::new(0), - path_resolution_count: AtomicU64::new(0), - path_component_count: AtomicU64::new(0), - path_cache_hits: AtomicU64::new(0), - path_cache_misses: AtomicU64::new(0), - negative_lookup_count: AtomicU64::new(0), - negative_cache_hits: AtomicU64::new(0), - negative_cache_misses: AtomicU64::new(0), - negative_cache_invalidations: AtomicU64::new(0), - attr_cache_hits: AtomicU64::new(0), - attr_cache_misses: AtomicU64::new(0), - dentry_cache_hits: AtomicU64::new(0), - dentry_cache_misses: AtomicU64::new(0), - chunk_read_queries: AtomicU64::new(0), - chunk_read_chunks: AtomicU64::new(0), - chunk_write_chunks: AtomicU64::new(0), - agentfs_batcher_enqueues: AtomicU64::new(0), - agentfs_batcher_drains_timer: AtomicU64::new(0), - agentfs_batcher_drains_bytes: AtomicU64::new(0), - agentfs_batcher_drains_explicit: AtomicU64::new(0), - agentfs_batcher_pending_max_bytes: AtomicU64::new(0), - agentfs_batcher_coalesced_ranges: AtomicU64::new(0), - agentfs_batcher_commit_latency_ns_total: AtomicU64::new(0), - agentfs_batcher_commit_txns: AtomicU64::new(0), - agentfs_batcher_txn_inodes_total: AtomicU64::new(0), - agentfs_batcher_txn_inodes_max: AtomicU64::new(0), - wal_checkpoint_count: AtomicU64::new(0), - wal_checkpoint_nanos: AtomicU64::new(0), - fuse_callback_count: AtomicU64::new(0), - fuse_lookup_count: AtomicU64::new(0), - fuse_getattr_count: AtomicU64::new(0), - fuse_readdir_count: AtomicU64::new(0), - fuse_readdir_plus_count: AtomicU64::new(0), - fuse_open_count: AtomicU64::new(0), - fuse_uring_requests: AtomicU64::new(0), - fuse_read_count: AtomicU64::new(0), - fuse_release_count: AtomicU64::new(0), - fuse_write_count: AtomicU64::new(0), - fuse_write_bytes: AtomicU64::new(0), - fuse_flush_count: AtomicU64::new(0), - fuse_flush_ranges: AtomicU64::new(0), - fuse_flush_bytes: AtomicU64::new(0), - fuse_noflush_enosys_replies: AtomicU64::new(0), - fuse_pending_tail_drains: AtomicU64::new(0), - fuse_noopen_enosys_replies: AtomicU64::new(0), - fuse_ino_file_resolutions: AtomicU64::new(0), - fuse_ino_file_upgrades: AtomicU64::new(0), - fuse_sync_inval_inode_ok: AtomicU64::new(0), - fuse_sync_inval_inode_err: AtomicU64::new(0), - fuse_sync_inval_entry_ok: AtomicU64::new(0), - fuse_sync_inval_entry_err: AtomicU64::new(0), - fuse_sync_inval_latency_ns_total: AtomicU64::new(0), - fuse_dispatch_wait_count: AtomicU64::new(0), - fuse_dispatch_wait_nanos: AtomicU64::new(0), - fuse_adapter_lock_wait_count: AtomicU64::new(0), - fuse_adapter_lock_wait_nanos: AtomicU64::new(0), - fuse_read_lane_wait_count: AtomicU64::new(0), - fuse_read_lane_wait_nanos: AtomicU64::new(0), - fuse_write_lane_wait_count: AtomicU64::new(0), - fuse_write_lane_wait_nanos: AtomicU64::new(0), - fuse_read_lane_max_concurrent: AtomicU64::new(0), - fuse_exclusive_fallback_count: AtomicU64::new(0), - fuse_workers_configured: AtomicU64::new(0), - fuse_worker_queue_depth_peak: AtomicU64::new(0), - fuse_dispatch_inline_fallback: AtomicU64::new(0), - fuse_dispatch_parallel_tasks: AtomicU64::new(0), - fuse_dispatch_max_concurrent: AtomicU64::new(0), - fuse_readdirplus_auto_requested: AtomicU64::new(0), - fuse_readdirplus_auto_enabled: AtomicU64::new(0), - fuse_readdirplus_do_requested: AtomicU64::new(0), - fuse_readdirplus_do_enabled: AtomicU64::new(0), - fuse_readdirplus_unsupported: AtomicU64::new(0), - fuse_readdirplus_mode: AtomicU64::new(0), - fuse_ttl_entry_ms: AtomicU64::new(0), - fuse_ttl_attr_ms: AtomicU64::new(0), - fuse_ttl_neg_ms: AtomicU64::new(0), - fuse_writeback_cache_enabled: AtomicU64::new(0), - fuse_keepcache_enabled: AtomicU64::new(0), - fuse_keepcache_eligibility_drops: AtomicU64::new(0), - fuse_adapter_entry_hits: AtomicU64::new(0), - fuse_adapter_entry_misses: AtomicU64::new(0), - fuse_adapter_attr_hits: AtomicU64::new(0), - fuse_adapter_attr_misses: AtomicU64::new(0), - fuse_adapter_negative_hits: AtomicU64::new(0), - fuse_adapter_negative_misses: AtomicU64::new(0), - fuse_adapter_inval_inode_notifications: AtomicU64::new(0), - fuse_adapter_inval_entry_notifications: AtomicU64::new(0), - base_fast_open_eligible: AtomicU64::new(0), - base_fast_open_keep_cache: AtomicU64::new(0), - base_fast_open_passthrough_attempted: AtomicU64::new(0), - base_fast_open_passthrough_succeeded: AtomicU64::new(0), - base_fast_open_passthrough_fallback: AtomicU64::new(0), - base_fast_open_rejected: AtomicU64::new(0), - base_fast_inode_invalidations: AtomicU64::new(0), - base_fast_stale_rejections: AtomicU64::new(0), - } - } - - fn add_connection_wait(&self, duration: Duration) { - self.connection_wait_count.fetch_add(1, Ordering::Relaxed); - self.connection_wait_nanos - .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed); - } - - fn add_connection_create(&self) { - self.connection_create_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_connection_reuse(&self) { - self.connection_reuse_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_lookup(&self) { - self.lookup_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_lookup_delta(&self) { - self.lookup_delta_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_lookup_base(&self) { - self.lookup_base_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_lookup_whiteout(&self) { - self.lookup_whiteout_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_getattr(&self) { - self.getattr_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_readdir(&self) { - self.readdir_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_readdir_plus(&self) { - self.readdir_plus_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_path_resolution(&self, components: u64) { - self.path_resolution_count.fetch_add(1, Ordering::Relaxed); - self.path_component_count - .fetch_add(components, Ordering::Relaxed); - } - - fn add_path_cache_hit(&self) { - self.path_cache_hits.fetch_add(1, Ordering::Relaxed); - } - - fn add_path_cache_miss(&self) { - self.path_cache_misses.fetch_add(1, Ordering::Relaxed); - } - - fn add_negative_lookup(&self) { - self.negative_lookup_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_negative_cache_hit(&self) { - self.negative_cache_hits.fetch_add(1, Ordering::Relaxed); - } - - fn add_negative_cache_miss(&self) { - self.negative_cache_misses.fetch_add(1, Ordering::Relaxed); - } - - fn add_negative_cache_invalidation(&self) { - self.negative_cache_invalidations - .fetch_add(1, Ordering::Relaxed); - } - - fn add_attr_cache_hit(&self) { - self.attr_cache_hits.fetch_add(1, Ordering::Relaxed); - } - - fn add_attr_cache_miss(&self) { - self.attr_cache_misses.fetch_add(1, Ordering::Relaxed); - } - - fn add_dentry_cache_hit(&self) { - self.dentry_cache_hits.fetch_add(1, Ordering::Relaxed); - } - - fn add_dentry_cache_miss(&self) { - self.dentry_cache_misses.fetch_add(1, Ordering::Relaxed); - } - - fn add_chunk_read_query(&self) { - self.chunk_read_queries.fetch_add(1, Ordering::Relaxed); - } - - fn add_chunk_read_chunks(&self, chunks: u64) { - self.chunk_read_chunks.fetch_add(chunks, Ordering::Relaxed); - } - - fn add_chunk_write_chunks(&self, chunks: u64) { - self.chunk_write_chunks.fetch_add(chunks, Ordering::Relaxed); - } - - fn add_agentfs_batcher_enqueue(&self) { - self.agentfs_batcher_enqueues - .fetch_add(1, Ordering::Relaxed); - } - - fn add_agentfs_batcher_drain_timer(&self) { - self.agentfs_batcher_drains_timer - .fetch_add(1, Ordering::Relaxed); - } - - fn add_agentfs_batcher_drain_bytes(&self) { - self.agentfs_batcher_drains_bytes - .fetch_add(1, Ordering::Relaxed); - } - - fn add_agentfs_batcher_drain_explicit(&self) { - self.agentfs_batcher_drains_explicit - .fetch_add(1, Ordering::Relaxed); - } - - fn update_agentfs_batcher_pending_max_bytes(&self, pending_bytes: u64) { - let mut current = self - .agentfs_batcher_pending_max_bytes - .load(Ordering::Relaxed); - while pending_bytes > current { - match self - .agentfs_batcher_pending_max_bytes - .compare_exchange_weak(current, pending_bytes, Ordering::Relaxed, Ordering::Relaxed) - { - Ok(_) => break, - Err(actual) => current = actual, - } - } - } - - fn add_agentfs_batcher_coalesced_ranges(&self, ranges: u64) { - self.agentfs_batcher_coalesced_ranges - .fetch_add(ranges, Ordering::Relaxed); - } - - fn add_agentfs_batcher_commit_latency(&self, duration: Duration) { - self.agentfs_batcher_commit_latency_ns_total - .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed); - } - - /// One batcher SQLite commit transaction covering `inodes` inodes. Counts - /// actual `BEGIN IMMEDIATE`/`COMMIT` pairs (not per-inode drain ticks) so - /// the transaction shape of the write batcher is directly observable. - fn add_agentfs_batcher_commit_txn(&self, inodes: u64) { - self.agentfs_batcher_commit_txns - .fetch_add(1, Ordering::Relaxed); - self.agentfs_batcher_txn_inodes_total - .fetch_add(inodes, Ordering::Relaxed); - let mut current = self.agentfs_batcher_txn_inodes_max.load(Ordering::Relaxed); - while inodes > current { - match self.agentfs_batcher_txn_inodes_max.compare_exchange_weak( - current, - inodes, - Ordering::Relaxed, - Ordering::Relaxed, - ) { - Ok(_) => break, - Err(actual) => current = actual, - } - } - } - - fn add_fuse_op(&self, slot: FuseOpSlot, duration: Duration) { - let idx = slot as usize; - self.fuse_op_counts[idx].fetch_add(1, Ordering::Relaxed); - self.fuse_op_nanos[idx].fetch_add(duration.as_nanos() as u64, Ordering::Relaxed); - } - - fn add_wal_checkpoint(&self, duration: Duration) { - self.wal_checkpoint_count.fetch_add(1, Ordering::Relaxed); - self.wal_checkpoint_nanos - .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed); - } - - fn add_fuse_callback(&self) { - self.fuse_callback_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_lookup(&self) { - self.add_fuse_callback(); - self.fuse_lookup_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_getattr(&self) { - self.add_fuse_callback(); - self.fuse_getattr_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_readdir(&self) { - self.add_fuse_callback(); - self.fuse_readdir_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_readdir_plus(&self) { - self.add_fuse_callback(); - self.fuse_readdir_plus_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_open(&self) { - self.add_fuse_callback(); - self.fuse_open_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_uring_request(&self) { - self.fuse_uring_requests.fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_read(&self) { - self.add_fuse_callback(); - self.fuse_read_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_release(&self) { - self.add_fuse_callback(); - self.fuse_release_count.fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_write(&self, bytes: u64) { - self.add_fuse_callback(); - self.fuse_write_count.fetch_add(1, Ordering::Relaxed); - self.fuse_write_bytes.fetch_add(bytes, Ordering::Relaxed); - } - - fn add_fuse_flush(&self, ranges: u64, bytes: u64) { - self.fuse_flush_count.fetch_add(1, Ordering::Relaxed); - self.fuse_flush_ranges.fetch_add(ranges, Ordering::Relaxed); - self.fuse_flush_bytes.fetch_add(bytes, Ordering::Relaxed); - } - - fn add_fuse_noflush_enosys_reply(&self) { - self.fuse_noflush_enosys_replies - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_pending_tail_drain(&self) { - self.fuse_pending_tail_drains - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_noopen_enosys_reply(&self) { - self.fuse_noopen_enosys_replies - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_ino_file_resolution(&self) { - self.fuse_ino_file_resolutions - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_ino_file_upgrade(&self) { - self.fuse_ino_file_upgrades.fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_sync_inval_inode_ok(&self) { - self.fuse_sync_inval_inode_ok - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_sync_inval_inode_err(&self) { - self.fuse_sync_inval_inode_err - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_sync_inval_entry_ok(&self) { - self.fuse_sync_inval_entry_ok - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_sync_inval_entry_err(&self) { - self.fuse_sync_inval_entry_err - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_sync_inval_latency(&self, duration: Duration) { - self.fuse_sync_inval_latency_ns_total - .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed); - } - - fn add_fuse_dispatch_wait(&self, duration: Duration) { - self.fuse_dispatch_wait_count - .fetch_add(1, Ordering::Relaxed); - self.fuse_dispatch_wait_nanos - .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed); - } - - fn add_fuse_adapter_lock_wait(&self, duration: Duration) { - self.fuse_adapter_lock_wait_count - .fetch_add(1, Ordering::Relaxed); - self.fuse_adapter_lock_wait_nanos - .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed); - } - - fn add_fuse_read_lane_wait(&self, duration: Duration) { - self.fuse_read_lane_wait_count - .fetch_add(1, Ordering::Relaxed); - self.fuse_read_lane_wait_nanos - .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed); - } - - fn add_fuse_write_lane_wait(&self, duration: Duration) { - self.fuse_write_lane_wait_count - .fetch_add(1, Ordering::Relaxed); - self.fuse_write_lane_wait_nanos - .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed); - } - - fn update_fuse_read_lane_max_concurrent(&self, concurrent: u64) { - let mut current = self.fuse_read_lane_max_concurrent.load(Ordering::Relaxed); - while concurrent > current { - match self.fuse_read_lane_max_concurrent.compare_exchange_weak( - current, - concurrent, - Ordering::Relaxed, - Ordering::Relaxed, - ) { - Ok(_) => break, - Err(actual) => current = actual, - } - } - } - - fn add_fuse_exclusive_fallback(&self) { - self.fuse_exclusive_fallback_count - .fetch_add(1, Ordering::Relaxed); - } - - fn set_fuse_workers_configured(&self, workers: u64) { - self.fuse_workers_configured - .store(workers, Ordering::Relaxed); - } - - fn update_fuse_worker_queue_depth_peak(&self, depth: u64) { - let mut current = self.fuse_worker_queue_depth_peak.load(Ordering::Relaxed); - while depth > current { - match self.fuse_worker_queue_depth_peak.compare_exchange_weak( - current, - depth, - Ordering::Relaxed, - Ordering::Relaxed, - ) { - Ok(_) => break, - Err(actual) => current = actual, - } - } - } - - fn add_fuse_dispatch_inline_fallback(&self) { - self.fuse_dispatch_inline_fallback - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_dispatch_parallel_task(&self) { - self.fuse_dispatch_parallel_tasks - .fetch_add(1, Ordering::Relaxed); - } - - fn update_fuse_dispatch_max_concurrent(&self, concurrent: u64) { - let mut current = self.fuse_dispatch_max_concurrent.load(Ordering::Relaxed); - while concurrent > current { - match self.fuse_dispatch_max_concurrent.compare_exchange_weak( - current, - concurrent, - Ordering::Relaxed, - Ordering::Relaxed, - ) { - Ok(_) => break, - Err(actual) => current = actual, - } - } - } - - fn add_fuse_readdirplus_auto_requested(&self) { - self.fuse_readdirplus_auto_requested - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_readdirplus_auto_enabled(&self) { - self.fuse_readdirplus_auto_enabled - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_readdirplus_do_requested(&self) { - self.fuse_readdirplus_do_requested - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_readdirplus_do_enabled(&self) { - self.fuse_readdirplus_do_enabled - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_readdirplus_unsupported(&self) { - self.fuse_readdirplus_unsupported - .fetch_add(1, Ordering::Relaxed); - } - - fn set_fuse_readdirplus_mode(&self, mode: u64) { - self.fuse_readdirplus_mode.store(mode, Ordering::Relaxed); - } - - fn set_fuse_ttl_ms(&self, entry_ms: u64, attr_ms: u64, neg_ms: u64) { - self.fuse_ttl_entry_ms.store(entry_ms, Ordering::Relaxed); - self.fuse_ttl_attr_ms.store(attr_ms, Ordering::Relaxed); - self.fuse_ttl_neg_ms.store(neg_ms, Ordering::Relaxed); - } - - fn set_fuse_writeback_cache_enabled(&self, enabled: bool) { - self.fuse_writeback_cache_enabled - .store(u64::from(enabled), Ordering::Relaxed); - } - - fn set_fuse_keepcache_enabled(&self, enabled: bool) { - self.fuse_keepcache_enabled - .store(u64::from(enabled), Ordering::Relaxed); - } - - fn add_fuse_keepcache_eligibility_drop(&self) { - self.fuse_keepcache_eligibility_drops - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_adapter_entry_hit(&self) { - self.fuse_adapter_entry_hits.fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_adapter_entry_miss(&self) { - self.fuse_adapter_entry_misses - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_adapter_attr_hit(&self) { - self.fuse_adapter_attr_hits.fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_adapter_attr_miss(&self) { - self.fuse_adapter_attr_misses - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_adapter_negative_hit(&self) { - self.fuse_adapter_negative_hits - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_adapter_negative_miss(&self) { - self.fuse_adapter_negative_misses - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_adapter_inval_inode_notification(&self) { - self.fuse_adapter_inval_inode_notifications - .fetch_add(1, Ordering::Relaxed); - } - - fn add_fuse_adapter_inval_entry_notification(&self) { - self.fuse_adapter_inval_entry_notifications - .fetch_add(1, Ordering::Relaxed); - } - - fn add_base_fast_open_eligible(&self) { - self.base_fast_open_eligible.fetch_add(1, Ordering::Relaxed); - } - - fn add_base_fast_open_keep_cache(&self) { - self.base_fast_open_keep_cache - .fetch_add(1, Ordering::Relaxed); - } - - fn add_base_fast_open_passthrough_attempted(&self) { - self.base_fast_open_passthrough_attempted - .fetch_add(1, Ordering::Relaxed); - } - - fn add_base_fast_open_passthrough_succeeded(&self) { - self.base_fast_open_passthrough_succeeded - .fetch_add(1, Ordering::Relaxed); - } - - fn add_base_fast_open_passthrough_fallback(&self) { - self.base_fast_open_passthrough_fallback - .fetch_add(1, Ordering::Relaxed); - } - - fn add_base_fast_open_rejected(&self) { - self.base_fast_open_rejected.fetch_add(1, Ordering::Relaxed); - } - - fn add_base_fast_inode_invalidation(&self) { - self.base_fast_inode_invalidations - .fetch_add(1, Ordering::Relaxed); - } - - fn add_base_fast_stale_rejection(&self) { - self.base_fast_stale_rejections - .fetch_add(1, Ordering::Relaxed); - } - - pub fn snapshot(&self) -> ProfileSnapshot { - ProfileSnapshot { - connection_wait_count: self.connection_wait_count.load(Ordering::Relaxed), - connection_wait_nanos: self.connection_wait_nanos.load(Ordering::Relaxed), - connection_create_count: self.connection_create_count.load(Ordering::Relaxed), - connection_reuse_count: self.connection_reuse_count.load(Ordering::Relaxed), - lookup_count: self.lookup_count.load(Ordering::Relaxed), - lookup_delta_count: self.lookup_delta_count.load(Ordering::Relaxed), - lookup_base_count: self.lookup_base_count.load(Ordering::Relaxed), - lookup_whiteout_count: self.lookup_whiteout_count.load(Ordering::Relaxed), - getattr_count: self.getattr_count.load(Ordering::Relaxed), - readdir_count: self.readdir_count.load(Ordering::Relaxed), - readdir_plus_count: self.readdir_plus_count.load(Ordering::Relaxed), - path_resolution_count: self.path_resolution_count.load(Ordering::Relaxed), - path_component_count: self.path_component_count.load(Ordering::Relaxed), - path_cache_hits: self.path_cache_hits.load(Ordering::Relaxed), - path_cache_misses: self.path_cache_misses.load(Ordering::Relaxed), - negative_lookup_count: self.negative_lookup_count.load(Ordering::Relaxed), - negative_cache_hits: self.negative_cache_hits.load(Ordering::Relaxed), - negative_cache_misses: self.negative_cache_misses.load(Ordering::Relaxed), - negative_cache_invalidations: self.negative_cache_invalidations.load(Ordering::Relaxed), - attr_cache_hits: self.attr_cache_hits.load(Ordering::Relaxed), - attr_cache_misses: self.attr_cache_misses.load(Ordering::Relaxed), - dentry_cache_hits: self.dentry_cache_hits.load(Ordering::Relaxed), - dentry_cache_misses: self.dentry_cache_misses.load(Ordering::Relaxed), - chunk_read_queries: self.chunk_read_queries.load(Ordering::Relaxed), - chunk_read_chunks: self.chunk_read_chunks.load(Ordering::Relaxed), - chunk_write_chunks: self.chunk_write_chunks.load(Ordering::Relaxed), - agentfs_batcher_enqueues: self.agentfs_batcher_enqueues.load(Ordering::Relaxed), - agentfs_batcher_drains_timer: self.agentfs_batcher_drains_timer.load(Ordering::Relaxed), - agentfs_batcher_drains_bytes: self.agentfs_batcher_drains_bytes.load(Ordering::Relaxed), - agentfs_batcher_drains_explicit: self - .agentfs_batcher_drains_explicit - .load(Ordering::Relaxed), - agentfs_batcher_pending_max_bytes: self - .agentfs_batcher_pending_max_bytes - .load(Ordering::Relaxed), - agentfs_batcher_coalesced_ranges: self - .agentfs_batcher_coalesced_ranges - .load(Ordering::Relaxed), - agentfs_batcher_commit_latency_ns_total: self - .agentfs_batcher_commit_latency_ns_total - .load(Ordering::Relaxed), - agentfs_batcher_commit_txns: self.agentfs_batcher_commit_txns.load(Ordering::Relaxed), - agentfs_batcher_txn_inodes_total: self - .agentfs_batcher_txn_inodes_total - .load(Ordering::Relaxed), - agentfs_batcher_txn_inodes_max: self - .agentfs_batcher_txn_inodes_max - .load(Ordering::Relaxed), - wal_checkpoint_count: self.wal_checkpoint_count.load(Ordering::Relaxed), - wal_checkpoint_nanos: self.wal_checkpoint_nanos.load(Ordering::Relaxed), - fuse_callback_count: self.fuse_callback_count.load(Ordering::Relaxed), - fuse_lookup_count: self.fuse_lookup_count.load(Ordering::Relaxed), - fuse_getattr_count: self.fuse_getattr_count.load(Ordering::Relaxed), - fuse_readdir_count: self.fuse_readdir_count.load(Ordering::Relaxed), - fuse_readdir_plus_count: self.fuse_readdir_plus_count.load(Ordering::Relaxed), - fuse_open_count: self.fuse_open_count.load(Ordering::Relaxed), - fuse_uring_requests: self.fuse_uring_requests.load(Ordering::Relaxed), - fuse_read_count: self.fuse_read_count.load(Ordering::Relaxed), - fuse_release_count: self.fuse_release_count.load(Ordering::Relaxed), - fuse_write_count: self.fuse_write_count.load(Ordering::Relaxed), - fuse_write_bytes: self.fuse_write_bytes.load(Ordering::Relaxed), - fuse_flush_count: self.fuse_flush_count.load(Ordering::Relaxed), - fuse_flush_ranges: self.fuse_flush_ranges.load(Ordering::Relaxed), - fuse_flush_bytes: self.fuse_flush_bytes.load(Ordering::Relaxed), - fuse_noflush_enosys_replies: self.fuse_noflush_enosys_replies.load(Ordering::Relaxed), - fuse_pending_tail_drains: self.fuse_pending_tail_drains.load(Ordering::Relaxed), - fuse_noopen_enosys_replies: self.fuse_noopen_enosys_replies.load(Ordering::Relaxed), - fuse_ino_file_resolutions: self.fuse_ino_file_resolutions.load(Ordering::Relaxed), - fuse_ino_file_upgrades: self.fuse_ino_file_upgrades.load(Ordering::Relaxed), - fuse_sync_inval_inode_ok: self.fuse_sync_inval_inode_ok.load(Ordering::Relaxed), - fuse_sync_inval_inode_err: self.fuse_sync_inval_inode_err.load(Ordering::Relaxed), - fuse_sync_inval_entry_ok: self.fuse_sync_inval_entry_ok.load(Ordering::Relaxed), - fuse_sync_inval_entry_err: self.fuse_sync_inval_entry_err.load(Ordering::Relaxed), - fuse_sync_inval_latency_ns_total: self - .fuse_sync_inval_latency_ns_total - .load(Ordering::Relaxed), - fuse_dispatch_wait_count: self.fuse_dispatch_wait_count.load(Ordering::Relaxed), - fuse_dispatch_wait_nanos: self.fuse_dispatch_wait_nanos.load(Ordering::Relaxed), - fuse_adapter_lock_wait_count: self.fuse_adapter_lock_wait_count.load(Ordering::Relaxed), - fuse_adapter_lock_wait_nanos: self.fuse_adapter_lock_wait_nanos.load(Ordering::Relaxed), - fuse_read_lane_wait_count: self.fuse_read_lane_wait_count.load(Ordering::Relaxed), - fuse_read_lane_wait_nanos: self.fuse_read_lane_wait_nanos.load(Ordering::Relaxed), - fuse_write_lane_wait_count: self.fuse_write_lane_wait_count.load(Ordering::Relaxed), - fuse_write_lane_wait_nanos: self.fuse_write_lane_wait_nanos.load(Ordering::Relaxed), - fuse_read_lane_max_concurrent: self - .fuse_read_lane_max_concurrent - .load(Ordering::Relaxed), - fuse_exclusive_fallback_count: self - .fuse_exclusive_fallback_count - .load(Ordering::Relaxed), - fuse_workers_configured: self.fuse_workers_configured.load(Ordering::Relaxed), - fuse_worker_queue_depth_peak: self.fuse_worker_queue_depth_peak.load(Ordering::Relaxed), - fuse_dispatch_inline_fallback: self - .fuse_dispatch_inline_fallback - .load(Ordering::Relaxed), - fuse_dispatch_parallel_tasks: self.fuse_dispatch_parallel_tasks.load(Ordering::Relaxed), - fuse_dispatch_max_concurrent: self.fuse_dispatch_max_concurrent.load(Ordering::Relaxed), - fuse_readdirplus_auto_requested: self - .fuse_readdirplus_auto_requested - .load(Ordering::Relaxed), - fuse_readdirplus_auto_enabled: self - .fuse_readdirplus_auto_enabled - .load(Ordering::Relaxed), - fuse_readdirplus_do_requested: self - .fuse_readdirplus_do_requested - .load(Ordering::Relaxed), - fuse_readdirplus_do_enabled: self.fuse_readdirplus_do_enabled.load(Ordering::Relaxed), - fuse_readdirplus_unsupported: self.fuse_readdirplus_unsupported.load(Ordering::Relaxed), - fuse_readdirplus_mode: self.fuse_readdirplus_mode.load(Ordering::Relaxed), - fuse_ttl_entry_ms: self.fuse_ttl_entry_ms.load(Ordering::Relaxed), - fuse_ttl_attr_ms: self.fuse_ttl_attr_ms.load(Ordering::Relaxed), - fuse_ttl_neg_ms: self.fuse_ttl_neg_ms.load(Ordering::Relaxed), - fuse_writeback_cache_enabled: self.fuse_writeback_cache_enabled.load(Ordering::Relaxed), - fuse_keepcache_enabled: self.fuse_keepcache_enabled.load(Ordering::Relaxed), - fuse_keepcache_eligibility_drops: self - .fuse_keepcache_eligibility_drops - .load(Ordering::Relaxed), - fuse_adapter_entry_hits: self.fuse_adapter_entry_hits.load(Ordering::Relaxed), - fuse_adapter_entry_misses: self.fuse_adapter_entry_misses.load(Ordering::Relaxed), - fuse_adapter_attr_hits: self.fuse_adapter_attr_hits.load(Ordering::Relaxed), - fuse_adapter_attr_misses: self.fuse_adapter_attr_misses.load(Ordering::Relaxed), - fuse_adapter_negative_hits: self.fuse_adapter_negative_hits.load(Ordering::Relaxed), - fuse_adapter_negative_misses: self.fuse_adapter_negative_misses.load(Ordering::Relaxed), - fuse_adapter_inval_inode_notifications: self - .fuse_adapter_inval_inode_notifications - .load(Ordering::Relaxed), - fuse_adapter_inval_entry_notifications: self - .fuse_adapter_inval_entry_notifications - .load(Ordering::Relaxed), - base_fast_open_eligible: self.base_fast_open_eligible.load(Ordering::Relaxed), - base_fast_open_keep_cache: self.base_fast_open_keep_cache.load(Ordering::Relaxed), - base_fast_open_passthrough_attempted: self - .base_fast_open_passthrough_attempted - .load(Ordering::Relaxed), - base_fast_open_passthrough_succeeded: self - .base_fast_open_passthrough_succeeded - .load(Ordering::Relaxed), - base_fast_open_passthrough_fallback: self - .base_fast_open_passthrough_fallback - .load(Ordering::Relaxed), - base_fast_open_rejected: self.base_fast_open_rejected.load(Ordering::Relaxed), - base_fast_inode_invalidations: self - .base_fast_inode_invalidations - .load(Ordering::Relaxed), - base_fast_stale_rejections: self.base_fast_stale_rejections.load(Ordering::Relaxed), - fuse_op_latency: { - let mut map = std::collections::BTreeMap::new(); - for (idx, name) in FUSE_OP_SLOT_NAMES.iter().enumerate() { - let count = self.fuse_op_counts[idx].load(Ordering::Relaxed); - if count > 0 { - map.insert(format!("fuse_op_{name}_count"), count); - map.insert( - format!("fuse_op_{name}_nanos"), - self.fuse_op_nanos[idx].load(Ordering::Relaxed), - ); - } - } - map - }, - } - } -} - -impl Default for ProfileCounters { - fn default() -> Self { - Self::new() - } -} - -/// Returns true when profiling is enabled with `AGENTFS_PROFILE=1`. -/// Always-on under `#[cfg(test)]` so unit tests can assert on counters -/// without racing the global `OnceCell` init. -pub fn is_enabled() -> bool { - #[cfg(test)] - { - true - } - #[cfg(not(test))] - { - *ENABLED.get_or_init(|| { - std::env::var("AGENTFS_PROFILE") - .map(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on")) - .unwrap_or(false) - }) - } -} - -pub fn record_connection_wait(duration: Duration) { - if is_enabled() { - COUNTERS.add_connection_wait(duration); - } -} - -pub fn record_connection_create() { - if is_enabled() { - COUNTERS.add_connection_create(); - } -} - -pub fn record_connection_reuse() { - if is_enabled() { - COUNTERS.add_connection_reuse(); - } -} - -pub fn record_lookup() { - if is_enabled() { - COUNTERS.add_lookup(); - } -} - -pub fn record_lookup_delta() { - if is_enabled() { - COUNTERS.add_lookup_delta(); - } -} - -pub fn record_lookup_base() { - if is_enabled() { - COUNTERS.add_lookup_base(); - } -} - -pub fn record_lookup_whiteout() { - if is_enabled() { - COUNTERS.add_lookup_whiteout(); - } -} - -pub fn record_getattr() { - if is_enabled() { - COUNTERS.add_getattr(); - } -} - -pub fn record_readdir() { - if is_enabled() { - COUNTERS.add_readdir(); - } -} - -pub fn record_readdir_plus() { - if is_enabled() { - COUNTERS.add_readdir_plus(); - } -} - -pub fn record_path_resolution(components: u64) { - if is_enabled() { - COUNTERS.add_path_resolution(components); - } -} - -pub fn record_path_cache_hit() { - if is_enabled() { - COUNTERS.add_path_cache_hit(); - } -} - -pub fn record_path_cache_miss() { - if is_enabled() { - COUNTERS.add_path_cache_miss(); - } -} - -pub fn record_negative_lookup() { - if is_enabled() { - COUNTERS.add_negative_lookup(); - } -} - -pub fn record_negative_cache_hit() { - if is_enabled() { - COUNTERS.add_negative_cache_hit(); - } -} - -pub fn record_negative_cache_miss() { - if is_enabled() { - COUNTERS.add_negative_cache_miss(); - } -} - -pub fn record_negative_cache_invalidation() { - if is_enabled() { - COUNTERS.add_negative_cache_invalidation(); - } -} - -pub fn record_attr_cache_hit() { - if is_enabled() { - COUNTERS.add_attr_cache_hit(); - } -} - -pub fn record_attr_cache_miss() { - if is_enabled() { - COUNTERS.add_attr_cache_miss(); - } -} - -pub fn record_dentry_cache_hit() { - if is_enabled() { - COUNTERS.add_dentry_cache_hit(); - } -} - -pub fn record_dentry_cache_miss() { - if is_enabled() { - COUNTERS.add_dentry_cache_miss(); - } -} - -pub fn record_chunk_read_query() { - if is_enabled() { - COUNTERS.add_chunk_read_query(); - } -} - -pub fn record_chunk_read_chunks(chunks: u64) { - if is_enabled() { - COUNTERS.add_chunk_read_chunks(chunks); - } -} - -pub fn record_chunk_write_chunks(chunks: u64) { - if is_enabled() { - COUNTERS.add_chunk_write_chunks(chunks); - } -} - -pub fn record_agentfs_batcher_enqueue() { - if is_enabled() { - COUNTERS.add_agentfs_batcher_enqueue(); - } -} - -pub fn record_agentfs_batcher_drain_timer() { - if is_enabled() { - COUNTERS.add_agentfs_batcher_drain_timer(); - } -} - -pub fn record_agentfs_batcher_drain_bytes() { - if is_enabled() { - COUNTERS.add_agentfs_batcher_drain_bytes(); - } -} - -pub fn record_agentfs_batcher_drain_explicit() { - if is_enabled() { - COUNTERS.add_agentfs_batcher_drain_explicit(); - } -} - -pub fn record_agentfs_batcher_pending_bytes(pending_bytes: u64) { - if is_enabled() { - COUNTERS.update_agentfs_batcher_pending_max_bytes(pending_bytes); - } -} - -pub fn record_agentfs_batcher_coalesced_ranges(ranges: u64) { - if is_enabled() && ranges > 0 { - COUNTERS.add_agentfs_batcher_coalesced_ranges(ranges); - } -} - -pub fn record_agentfs_batcher_commit_latency(duration: Duration) { - if is_enabled() { - COUNTERS.add_agentfs_batcher_commit_latency(duration); - } -} - -/// Record one batcher SQLite commit transaction that covered `inodes` inodes. -pub fn record_agentfs_batcher_commit_txn(inodes: u64) { - if is_enabled() { - COUNTERS.add_agentfs_batcher_commit_txn(inodes); - } -} - -/// Record one FUSE request's full dispatch latency (parse → handler → reply). -pub fn record_fuse_op(slot: FuseOpSlot, duration: Duration) { - if is_enabled() { - COUNTERS.add_fuse_op(slot, duration); - } -} - -pub fn record_wal_checkpoint(duration: Duration) { - if is_enabled() { - COUNTERS.add_wal_checkpoint(duration); - } -} - -pub fn record_fuse_lookup() { - if is_enabled() { - COUNTERS.add_fuse_lookup(); - } -} - -pub fn record_fuse_getattr() { - if is_enabled() { - COUNTERS.add_fuse_getattr(); - } -} - -pub fn record_fuse_readdir() { - if is_enabled() { - COUNTERS.add_fuse_readdir(); - } -} - -pub fn record_fuse_readdir_plus() { - if is_enabled() { - COUNTERS.add_fuse_readdir_plus(); - } -} - -pub fn record_fuse_open() { - if is_enabled() { - COUNTERS.add_fuse_open(); - } -} - -/// Count a FUSE request delivered via the fuse-over-io_uring transport. -pub fn record_fuse_uring_request() { - if is_enabled() { - COUNTERS.add_fuse_uring_request(); - } -} - -pub fn record_fuse_read() { - if is_enabled() { - COUNTERS.add_fuse_read(); - } -} - -pub fn record_fuse_release() { - if is_enabled() { - COUNTERS.add_fuse_release(); - } -} - -pub fn record_fuse_write(bytes: u64) { - if is_enabled() { - COUNTERS.add_fuse_write(bytes); - } -} - -pub fn record_fuse_flush(ranges: u64, bytes: u64) { - if is_enabled() { - COUNTERS.add_fuse_flush(ranges, bytes); - } -} - -pub fn record_fuse_noflush_enosys_reply() { - if is_enabled() { - COUNTERS.add_fuse_noflush_enosys_reply(); - } -} - -pub fn record_fuse_pending_tail_drain() { - if is_enabled() { - COUNTERS.add_fuse_pending_tail_drain(); - } -} - -pub fn record_fuse_noopen_enosys_reply() { - if is_enabled() { - COUNTERS.add_fuse_noopen_enosys_reply(); - } -} - -pub fn record_fuse_ino_file_resolution() { - if is_enabled() { - COUNTERS.add_fuse_ino_file_resolution(); - } -} - -pub fn record_fuse_ino_file_upgrade() { - if is_enabled() { - COUNTERS.add_fuse_ino_file_upgrade(); - } -} - -pub fn record_fuse_sync_inval_inode_ok() { - if is_enabled() { - COUNTERS.add_fuse_sync_inval_inode_ok(); - } -} - -pub fn record_fuse_sync_inval_inode_err() { - if is_enabled() { - COUNTERS.add_fuse_sync_inval_inode_err(); - } -} - -pub fn record_fuse_sync_inval_entry_ok() { - if is_enabled() { - COUNTERS.add_fuse_sync_inval_entry_ok(); - } -} - -pub fn record_fuse_sync_inval_entry_err() { - if is_enabled() { - COUNTERS.add_fuse_sync_inval_entry_err(); - } -} - -pub fn record_fuse_sync_inval_latency(duration: Duration) { - if is_enabled() { - COUNTERS.add_fuse_sync_inval_latency(duration); - } -} - -pub fn record_fuse_dispatch_wait(duration: Duration) { - if is_enabled() { - COUNTERS.add_fuse_dispatch_wait(duration); - } -} - -pub fn record_fuse_adapter_lock_wait(duration: Duration) { - if is_enabled() { - COUNTERS.add_fuse_adapter_lock_wait(duration); - } -} - -pub fn record_fuse_read_lane_wait(duration: Duration) { - if is_enabled() { - COUNTERS.add_fuse_read_lane_wait(duration); - } -} - -pub fn record_fuse_write_lane_wait(duration: Duration) { - if is_enabled() { - COUNTERS.add_fuse_write_lane_wait(duration); - } -} - -pub fn record_fuse_read_lane_concurrency(concurrent: u64) { - if is_enabled() { - COUNTERS.update_fuse_read_lane_max_concurrent(concurrent); - } -} - -pub fn record_fuse_exclusive_fallback() { - if is_enabled() { - COUNTERS.add_fuse_exclusive_fallback(); - } -} - -pub fn set_fuse_workers_configured(workers: u64) { - if is_enabled() { - COUNTERS.set_fuse_workers_configured(workers); - } -} - -pub fn record_fuse_worker_queue_depth(depth: u64) { - if is_enabled() { - COUNTERS.update_fuse_worker_queue_depth_peak(depth); - } -} - -pub fn record_fuse_dispatch_inline_fallback() { - if is_enabled() { - COUNTERS.add_fuse_dispatch_inline_fallback(); - } -} - -pub fn record_fuse_dispatch_parallel_task() { - if is_enabled() { - COUNTERS.add_fuse_dispatch_parallel_task(); - } -} - -pub fn record_fuse_dispatch_concurrency(concurrent: u64) { - if is_enabled() { - COUNTERS.update_fuse_dispatch_max_concurrent(concurrent); - } -} - -pub fn record_fuse_readdirplus_auto_requested() { - if is_enabled() { - COUNTERS.add_fuse_readdirplus_auto_requested(); - } -} - -pub fn record_fuse_readdirplus_auto_enabled() { - if is_enabled() { - COUNTERS.add_fuse_readdirplus_auto_enabled(); - } -} - -pub fn record_fuse_readdirplus_do_requested() { - if is_enabled() { - COUNTERS.add_fuse_readdirplus_do_requested(); - } -} - -pub fn record_fuse_readdirplus_do_enabled() { - if is_enabled() { - COUNTERS.add_fuse_readdirplus_do_enabled(); - } -} - -pub fn record_fuse_readdirplus_unsupported() { - if is_enabled() { - COUNTERS.add_fuse_readdirplus_unsupported(); - } -} - -pub fn set_fuse_readdirplus_mode(mode: u64) { - if is_enabled() { - COUNTERS.set_fuse_readdirplus_mode(mode); - } -} - -pub fn set_fuse_ttl_ms(entry_ms: u64, attr_ms: u64, neg_ms: u64) { - if is_enabled() { - COUNTERS.set_fuse_ttl_ms(entry_ms, attr_ms, neg_ms); - } -} - -pub fn set_fuse_writeback_cache_enabled(enabled: bool) { - if is_enabled() { - COUNTERS.set_fuse_writeback_cache_enabled(enabled); - } -} - -pub fn set_fuse_keepcache_enabled(enabled: bool) { - if is_enabled() { - COUNTERS.set_fuse_keepcache_enabled(enabled); - } -} - -pub fn record_fuse_keepcache_eligibility_drop() { - if is_enabled() { - COUNTERS.add_fuse_keepcache_eligibility_drop(); - } -} - -pub fn record_fuse_adapter_entry_hit() { - if is_enabled() { - COUNTERS.add_fuse_adapter_entry_hit(); - } -} - -pub fn record_fuse_adapter_entry_miss() { - if is_enabled() { - COUNTERS.add_fuse_adapter_entry_miss(); - } -} - -pub fn record_fuse_adapter_attr_hit() { - if is_enabled() { - COUNTERS.add_fuse_adapter_attr_hit(); - } -} - -pub fn record_fuse_adapter_attr_miss() { - if is_enabled() { - COUNTERS.add_fuse_adapter_attr_miss(); - } -} - -pub fn record_fuse_adapter_negative_hit() { - if is_enabled() { - COUNTERS.add_fuse_adapter_negative_hit(); - } -} - -pub fn record_fuse_adapter_negative_miss() { - if is_enabled() { - COUNTERS.add_fuse_adapter_negative_miss(); - } -} - -pub fn record_fuse_adapter_inval_inode_notification() { - if is_enabled() { - COUNTERS.add_fuse_adapter_inval_inode_notification(); - } -} - -pub fn record_fuse_adapter_inval_entry_notification() { - if is_enabled() { - COUNTERS.add_fuse_adapter_inval_entry_notification(); - } -} - -pub fn record_base_fast_open_eligible() { - if is_enabled() { - COUNTERS.add_base_fast_open_eligible(); - } -} - -pub fn record_base_fast_open_keep_cache() { - if is_enabled() { - COUNTERS.add_base_fast_open_keep_cache(); - } -} - -pub fn record_base_fast_open_passthrough_attempted() { - if is_enabled() { - COUNTERS.add_base_fast_open_passthrough_attempted(); - } -} - -pub fn record_base_fast_open_passthrough_succeeded() { - if is_enabled() { - COUNTERS.add_base_fast_open_passthrough_succeeded(); - } -} - -pub fn record_base_fast_open_passthrough_fallback() { - if is_enabled() { - COUNTERS.add_base_fast_open_passthrough_fallback(); - } -} - -pub fn record_base_fast_open_rejected() { - if is_enabled() { - COUNTERS.add_base_fast_open_rejected(); - } -} - -pub fn record_base_fast_inode_invalidation() { - if is_enabled() { - COUNTERS.add_base_fast_inode_invalidation(); - } -} - -pub fn record_base_fast_stale_rejection() { - if is_enabled() { - COUNTERS.add_base_fast_stale_rejection(); - } -} - -pub fn snapshot() -> ProfileSnapshot { - COUNTERS.snapshot() -} - -pub const fn passthrough_supported() -> bool { - false -} - -pub const fn passthrough_fallback_read_path() -> &'static str { - "hostfs" -} - -fn summary_json(source: &str, snapshot: &ProfileSnapshot) -> String { - serde_json::json!({ - "event": "agentfs_profile_summary", - "source": source, - "counters": snapshot, - "passthrough_supported": passthrough_supported(), - "fallback_read_path": passthrough_fallback_read_path(), - }) - .to_string() -} - -/// Emit a structured profile summary to stderr if profiling is enabled. -pub fn report_summary(source: &str) { - if !is_enabled() { - return; - } - - eprintln!("{}", summary_json(source, &snapshot())); -} - -/// Monotonic sequence for phase-boundary profile checkpoints. -static CHECKPOINT_SEQ: AtomicU64 = AtomicU64::new(0); - -/// Emit a cumulative profile summary tagged with a monotonic sequence number. -/// -/// Used to attribute counters to workload phases: a consumer subtracts -/// consecutive checkpoint snapshots to obtain per-phase deltas. The sequence -/// number makes ordering unambiguous even if stderr lines interleave. -pub fn report_checkpoint() { - if !is_enabled() { - return; - } - - let seq = CHECKPOINT_SEQ.fetch_add(1, Ordering::Relaxed) + 1; - eprintln!( - "{}", - summary_json(&format!("phase-checkpoint-{seq}"), &snapshot()) - ); -} - -/// Drop guard that emits the current profiling summary. -#[derive(Debug)] -pub struct ProfileReportGuard { - source: &'static str, -} - -impl ProfileReportGuard { - pub fn new(source: &'static str) -> Self { - Self { source } - } -} - -impl Drop for ProfileReportGuard { - fn drop(&mut self) { - report_summary(self.source); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_json::Value; - - #[test] - fn counters_accumulate_expected_values() { - let counters = ProfileCounters::new(); - - counters.add_connection_wait(Duration::from_nanos(7)); - counters.add_connection_create(); - counters.add_connection_reuse(); - counters.add_lookup(); - counters.add_lookup_delta(); - counters.add_lookup_base(); - counters.add_lookup_whiteout(); - counters.add_getattr(); - counters.add_readdir(); - counters.add_readdir_plus(); - counters.add_path_resolution(4); - counters.add_path_cache_hit(); - counters.add_path_cache_miss(); - counters.add_negative_lookup(); - counters.add_negative_cache_hit(); - counters.add_negative_cache_miss(); - counters.add_negative_cache_invalidation(); - counters.add_attr_cache_hit(); - counters.add_attr_cache_miss(); - counters.add_dentry_cache_hit(); - counters.add_dentry_cache_miss(); - counters.add_chunk_read_query(); - counters.add_chunk_read_chunks(3); - counters.add_chunk_write_chunks(5); - counters.add_agentfs_batcher_enqueue(); - counters.add_agentfs_batcher_drain_timer(); - counters.add_agentfs_batcher_drain_bytes(); - counters.add_agentfs_batcher_drain_explicit(); - counters.update_agentfs_batcher_pending_max_bytes(64); - counters.update_agentfs_batcher_pending_max_bytes(32); - counters.add_agentfs_batcher_coalesced_ranges(2); - counters.add_agentfs_batcher_commit_latency(Duration::from_nanos(17)); - counters.add_agentfs_batcher_commit_txn(3); - counters.add_agentfs_batcher_commit_txn(9); - counters.add_wal_checkpoint(Duration::from_nanos(11)); - counters.add_fuse_lookup(); - counters.add_fuse_getattr(); - counters.add_fuse_readdir(); - counters.add_fuse_readdir_plus(); - counters.add_fuse_open(); - counters.add_fuse_read(); - counters.add_fuse_release(); - counters.add_fuse_write(13); - counters.add_fuse_flush(2, 21); - counters.add_fuse_sync_inval_inode_ok(); - counters.add_fuse_sync_inval_inode_err(); - counters.add_fuse_sync_inval_entry_ok(); - counters.add_fuse_sync_inval_entry_err(); - counters.add_fuse_sync_inval_latency(Duration::from_nanos(29)); - counters.add_fuse_dispatch_wait(Duration::from_nanos(31)); - counters.add_fuse_adapter_lock_wait(Duration::from_nanos(37)); - counters.add_fuse_read_lane_wait(Duration::from_nanos(41)); - counters.add_fuse_write_lane_wait(Duration::from_nanos(43)); - counters.update_fuse_read_lane_max_concurrent(2); - counters.update_fuse_read_lane_max_concurrent(5); - counters.update_fuse_read_lane_max_concurrent(3); - counters.add_fuse_exclusive_fallback(); - counters.set_fuse_workers_configured(4); - counters.update_fuse_worker_queue_depth_peak(7); - counters.update_fuse_worker_queue_depth_peak(5); - counters.add_fuse_dispatch_inline_fallback(); - counters.add_fuse_dispatch_parallel_task(); - counters.add_fuse_dispatch_parallel_task(); - counters.update_fuse_dispatch_max_concurrent(3); - counters.update_fuse_dispatch_max_concurrent(6); - counters.update_fuse_dispatch_max_concurrent(2); - counters.set_fuse_readdirplus_mode(1); - counters.set_fuse_ttl_ms(1000, 750, 250); - counters.set_fuse_writeback_cache_enabled(true); - counters.set_fuse_keepcache_enabled(true); - counters.add_fuse_keepcache_eligibility_drop(); - counters.add_fuse_adapter_entry_hit(); - counters.add_fuse_adapter_entry_miss(); - counters.add_fuse_adapter_attr_hit(); - counters.add_fuse_adapter_attr_miss(); - counters.add_fuse_adapter_negative_hit(); - counters.add_fuse_adapter_negative_miss(); - counters.add_fuse_adapter_inval_inode_notification(); - counters.add_fuse_adapter_inval_entry_notification(); - counters.add_base_fast_open_eligible(); - counters.add_base_fast_open_keep_cache(); - counters.add_base_fast_open_passthrough_attempted(); - counters.add_base_fast_open_passthrough_succeeded(); - counters.add_base_fast_open_passthrough_fallback(); - counters.add_base_fast_open_rejected(); - counters.add_base_fast_inode_invalidation(); - counters.add_base_fast_stale_rejection(); - - let snapshot = counters.snapshot(); - assert_eq!(snapshot.connection_wait_count, 1); - assert_eq!(snapshot.connection_wait_nanos, 7); - assert_eq!(snapshot.connection_create_count, 1); - assert_eq!(snapshot.connection_reuse_count, 1); - assert_eq!(snapshot.lookup_count, 1); - assert_eq!(snapshot.lookup_delta_count, 1); - assert_eq!(snapshot.lookup_base_count, 1); - assert_eq!(snapshot.lookup_whiteout_count, 1); - assert_eq!(snapshot.getattr_count, 1); - assert_eq!(snapshot.readdir_count, 1); - assert_eq!(snapshot.readdir_plus_count, 1); - assert_eq!(snapshot.path_resolution_count, 1); - assert_eq!(snapshot.path_component_count, 4); - assert_eq!(snapshot.path_cache_hits, 1); - assert_eq!(snapshot.path_cache_misses, 1); - assert_eq!(snapshot.negative_lookup_count, 1); - assert_eq!(snapshot.negative_cache_hits, 1); - assert_eq!(snapshot.negative_cache_misses, 1); - assert_eq!(snapshot.negative_cache_invalidations, 1); - assert_eq!(snapshot.attr_cache_hits, 1); - assert_eq!(snapshot.attr_cache_misses, 1); - assert_eq!(snapshot.dentry_cache_hits, 1); - assert_eq!(snapshot.dentry_cache_misses, 1); - assert_eq!(snapshot.chunk_read_queries, 1); - assert_eq!(snapshot.chunk_read_chunks, 3); - assert_eq!(snapshot.chunk_write_chunks, 5); - assert_eq!(snapshot.agentfs_batcher_enqueues, 1); - assert_eq!(snapshot.agentfs_batcher_drains_timer, 1); - assert_eq!(snapshot.agentfs_batcher_drains_bytes, 1); - assert_eq!(snapshot.agentfs_batcher_drains_explicit, 1); - assert_eq!(snapshot.agentfs_batcher_pending_max_bytes, 64); - assert_eq!(snapshot.agentfs_batcher_coalesced_ranges, 2); - assert_eq!(snapshot.agentfs_batcher_commit_latency_ns_total, 17); - assert_eq!(snapshot.agentfs_batcher_commit_txns, 2); - assert_eq!(snapshot.agentfs_batcher_txn_inodes_total, 12); - assert_eq!(snapshot.agentfs_batcher_txn_inodes_max, 9); - assert_eq!(snapshot.wal_checkpoint_count, 1); - assert_eq!(snapshot.wal_checkpoint_nanos, 11); - assert_eq!(snapshot.fuse_callback_count, 8); - assert_eq!(snapshot.fuse_lookup_count, 1); - assert_eq!(snapshot.fuse_getattr_count, 1); - assert_eq!(snapshot.fuse_readdir_count, 1); - assert_eq!(snapshot.fuse_readdir_plus_count, 1); - assert_eq!(snapshot.fuse_open_count, 1); - assert_eq!(snapshot.fuse_read_count, 1); - assert_eq!(snapshot.fuse_release_count, 1); - assert_eq!(snapshot.fuse_write_count, 1); - assert_eq!(snapshot.fuse_write_bytes, 13); - assert_eq!(snapshot.fuse_flush_count, 1); - assert_eq!(snapshot.fuse_flush_ranges, 2); - assert_eq!(snapshot.fuse_flush_bytes, 21); - assert_eq!(snapshot.fuse_sync_inval_inode_ok, 1); - assert_eq!(snapshot.fuse_sync_inval_inode_err, 1); - assert_eq!(snapshot.fuse_sync_inval_entry_ok, 1); - assert_eq!(snapshot.fuse_sync_inval_entry_err, 1); - assert_eq!(snapshot.fuse_sync_inval_latency_ns_total, 29); - assert_eq!(snapshot.fuse_dispatch_wait_count, 1); - assert_eq!(snapshot.fuse_dispatch_wait_nanos, 31); - assert_eq!(snapshot.fuse_adapter_lock_wait_count, 1); - assert_eq!(snapshot.fuse_adapter_lock_wait_nanos, 37); - assert_eq!(snapshot.fuse_read_lane_wait_count, 1); - assert_eq!(snapshot.fuse_read_lane_wait_nanos, 41); - assert_eq!(snapshot.fuse_write_lane_wait_count, 1); - assert_eq!(snapshot.fuse_write_lane_wait_nanos, 43); - assert_eq!(snapshot.fuse_read_lane_max_concurrent, 5); - assert_eq!(snapshot.fuse_exclusive_fallback_count, 1); - assert_eq!(snapshot.fuse_workers_configured, 4); - assert_eq!(snapshot.fuse_worker_queue_depth_peak, 7); - assert_eq!(snapshot.fuse_dispatch_inline_fallback, 1); - assert_eq!(snapshot.fuse_dispatch_parallel_tasks, 2); - assert_eq!(snapshot.fuse_dispatch_max_concurrent, 6); - assert_eq!(snapshot.fuse_readdirplus_mode, 1); - assert_eq!(snapshot.fuse_ttl_entry_ms, 1000); - assert_eq!(snapshot.fuse_ttl_attr_ms, 750); - assert_eq!(snapshot.fuse_ttl_neg_ms, 250); - assert_eq!(snapshot.fuse_writeback_cache_enabled, 1); - assert_eq!(snapshot.fuse_keepcache_enabled, 1); - assert_eq!(snapshot.fuse_keepcache_eligibility_drops, 1); - assert_eq!(snapshot.fuse_adapter_entry_hits, 1); - assert_eq!(snapshot.fuse_adapter_entry_misses, 1); - assert_eq!(snapshot.fuse_adapter_attr_hits, 1); - assert_eq!(snapshot.fuse_adapter_attr_misses, 1); - assert_eq!(snapshot.fuse_adapter_negative_hits, 1); - assert_eq!(snapshot.fuse_adapter_negative_misses, 1); - assert_eq!(snapshot.fuse_adapter_inval_inode_notifications, 1); - assert_eq!(snapshot.fuse_adapter_inval_entry_notifications, 1); - assert_eq!(snapshot.base_fast_open_eligible, 1); - assert_eq!(snapshot.base_fast_open_keep_cache, 1); - assert_eq!(snapshot.base_fast_open_passthrough_attempted, 1); - assert_eq!(snapshot.base_fast_open_passthrough_succeeded, 1); - assert_eq!(snapshot.base_fast_open_passthrough_fallback, 1); - assert_eq!(snapshot.base_fast_open_rejected, 1); - assert_eq!(snapshot.base_fast_inode_invalidations, 1); - assert_eq!(snapshot.base_fast_stale_rejections, 1); - } - - #[test] - fn summary_json_is_structured() { - let counters = ProfileCounters::new(); - counters.add_chunk_read_query(); - - let value: Value = serde_json::from_str(&summary_json("unit-test", &counters.snapshot())) - .expect("summary JSON should parse"); - - assert_eq!(value["event"], "agentfs_profile_summary"); - assert_eq!(value["source"], "unit-test"); - assert_eq!(value["counters"]["chunk_read_queries"], 1); - } - - #[test] - fn summary_json_includes_phase65_fast_path_counters() { - let counters = ProfileCounters::new(); - counters.add_fuse_dispatch_wait(Duration::from_nanos(5)); - counters.add_fuse_adapter_lock_wait(Duration::from_nanos(6)); - counters.add_fuse_read_lane_wait(Duration::from_nanos(7)); - counters.add_fuse_write_lane_wait(Duration::from_nanos(8)); - counters.update_fuse_read_lane_max_concurrent(3); - counters.add_fuse_exclusive_fallback(); - counters.set_fuse_workers_configured(4); - counters.update_fuse_worker_queue_depth_peak(9); - counters.add_fuse_dispatch_inline_fallback(); - counters.add_fuse_dispatch_parallel_task(); - counters.update_fuse_dispatch_max_concurrent(5); - counters.set_fuse_readdirplus_mode(2); - counters.set_fuse_ttl_ms(1000, 1000, 500); - counters.set_fuse_writeback_cache_enabled(true); - counters.set_fuse_keepcache_enabled(true); - counters.add_fuse_keepcache_eligibility_drop(); - counters.add_base_fast_open_eligible(); - counters.add_base_fast_open_keep_cache(); - counters.add_base_fast_open_passthrough_fallback(); - counters.add_base_fast_open_rejected(); - counters.add_base_fast_inode_invalidation(); - counters.add_base_fast_stale_rejection(); - - let value: Value = serde_json::from_str(&summary_json("unit-test", &counters.snapshot())) - .expect("summary JSON should parse"); - let counters = &value["counters"]; - - assert_eq!(counters["fuse_dispatch_wait_count"], 1); - assert_eq!(counters["fuse_dispatch_wait_nanos"], 5); - assert_eq!(counters["fuse_adapter_lock_wait_count"], 1); - assert_eq!(counters["fuse_adapter_lock_wait_nanos"], 6); - assert_eq!(counters["fuse_read_lane_wait_count"], 1); - assert_eq!(counters["fuse_read_lane_wait_nanos"], 7); - assert_eq!(counters["fuse_write_lane_wait_count"], 1); - assert_eq!(counters["fuse_write_lane_wait_nanos"], 8); - assert_eq!(counters["fuse_read_lane_max_concurrent"], 3); - assert_eq!(counters["fuse_exclusive_fallback_count"], 1); - assert_eq!(counters["fuse_workers_configured"], 4); - assert_eq!(counters["fuse_worker_queue_depth_peak"], 9); - assert_eq!(counters["fuse_dispatch_inline_fallback"], 1); - assert_eq!(counters["fuse_dispatch_parallel_tasks"], 1); - assert_eq!(counters["fuse_dispatch_max_concurrent"], 5); - assert_eq!(counters["fuse_readdirplus_mode"], 2); - assert_eq!(counters["fuse_ttl_entry_ms"], 1000); - assert_eq!(counters["fuse_ttl_attr_ms"], 1000); - assert_eq!(counters["fuse_ttl_neg_ms"], 500); - assert_eq!(counters["fuse_writeback_cache_enabled"], 1); - assert_eq!(counters["fuse_keepcache_enabled"], 1); - assert_eq!(counters["fuse_keepcache_eligibility_drops"], 1); - assert_eq!(counters["base_fast_open_eligible"], 1); - assert_eq!(counters["base_fast_open_keep_cache"], 1); - assert_eq!(counters["base_fast_open_passthrough_attempted"], 0); - assert_eq!(counters["base_fast_open_passthrough_succeeded"], 0); - assert_eq!(counters["base_fast_open_passthrough_fallback"], 1); - assert_eq!(counters["base_fast_open_rejected"], 1); - assert_eq!(counters["base_fast_inode_invalidations"], 1); - assert_eq!(counters["base_fast_stale_rejections"], 1); - } -} diff --git a/sdk/rust/src/schema.rs b/sdk/rust/src/schema.rs deleted file mode 100644 index 1214f24c..00000000 --- a/sdk/rust/src/schema.rs +++ /dev/null @@ -1,158 +0,0 @@ -//! Schema versioning and detection for AgentFS databases. - -use crate::error::{Error, Result}; -use turso::Connection; - -/// Current schema version. -pub const AGENTFS_SCHEMA_VERSION: &str = "0.5"; - -/// Detected schema version based on column introspection. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SchemaVersion { - /// Base schema: fs_inode, fs_dentry, fs_data, fs_symlink, fs_config, kv_store, tool_calls - V0_0, - /// Added nlink column to fs_inode - V0_2, - /// Added atime_nsec, mtime_nsec, ctime_nsec, rdev columns to fs_inode - V0_4, - /// Added inline small-file storage columns to fs_inode - V0_5, -} - -impl std::fmt::Display for SchemaVersion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SchemaVersion::V0_0 => write!(f, "0.0"), - SchemaVersion::V0_2 => write!(f, "0.2"), - SchemaVersion::V0_4 => write!(f, "0.4"), - SchemaVersion::V0_5 => write!(f, "0.5"), - } - } -} - -impl SchemaVersion { - /// Returns the version string. - pub fn as_str(&self) -> &'static str { - match self { - SchemaVersion::V0_0 => "0.0", - SchemaVersion::V0_2 => "0.2", - SchemaVersion::V0_4 => "0.4", - SchemaVersion::V0_5 => "0.5", - } - } - - /// Returns true if this version is the current version. - pub fn is_current(&self) -> bool { - matches!(self, SchemaVersion::V0_5) - } -} - -/// Column information from PRAGMA table_info. -#[derive(Debug)] -struct ColumnInfo { - name: String, -} - -/// Detect the schema version of an existing database by introspecting fs_inode columns. -/// Returns None if the database has no fs_inode table (new database). -pub async fn detect_schema_version(conn: &Connection) -> Result> { - // Check if fs_inode table exists - let mut rows = conn - .query( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fs_inode'", - (), - ) - .await?; - - if rows.next().await?.is_none() { - // New database - return Ok(None); - } - - // Get columns from fs_inode table - let columns = get_table_columns(conn, "fs_inode").await?; - - let has_nlink = columns.iter().any(|c| c.name == "nlink"); - let has_atime_nsec = columns.iter().any(|c| c.name == "atime_nsec"); - let has_mtime_nsec = columns.iter().any(|c| c.name == "mtime_nsec"); - let has_ctime_nsec = columns.iter().any(|c| c.name == "ctime_nsec"); - let has_rdev = columns.iter().any(|c| c.name == "rdev"); - let has_data_inline = columns.iter().any(|c| c.name == "data_inline"); - let has_storage_kind = columns.iter().any(|c| c.name == "storage_kind"); - - // v0.5: explicit inline storage columns plus v0.5 fs_config markers - if has_data_inline - && has_storage_kind - && config_value(conn, "schema_version").await?.as_deref() == Some("0.5") - && config_value(conn, "inline_threshold").await?.is_some() - { - return Ok(Some(SchemaVersion::V0_5)); - } - - // v0.4: has all nsec columns and rdev - if has_atime_nsec && has_mtime_nsec && has_ctime_nsec && has_rdev { - return Ok(Some(SchemaVersion::V0_4)); - } - - // v0.2: has nlink but not the nsec columns - if has_nlink { - return Ok(Some(SchemaVersion::V0_2)); - } - - // v0.0: base schema - Ok(Some(SchemaVersion::V0_0)) -} - -/// Check that a database has a compatible schema version. -/// Returns Ok(()) for new databases or databases at the current version. -/// Returns Err(SchemaVersionMismatch) for databases with old schemas. -pub async fn check_schema_version(conn: &Connection) -> Result<()> { - if let Some(version) = detect_schema_version(conn).await? { - if !version.is_current() { - return Err(Error::SchemaVersionMismatch { - found: version.to_string(), - expected: AGENTFS_SCHEMA_VERSION.to_string(), - }); - } - } - Ok(()) -} - -/// Get column information for a table using PRAGMA table_info. -async fn get_table_columns(conn: &Connection, table_name: &str) -> Result> { - let mut rows = conn - .query(&format!("PRAGMA table_info({})", table_name), ()) - .await?; - - let mut columns = Vec::new(); - while let Some(row) = rows.next().await? { - let name: String = row.get(1)?; - columns.push(ColumnInfo { name }); - } - - Ok(columns) -} - -/// Read a text value from fs_config if the table/key exists. -async fn config_value(conn: &Connection, key: &str) -> Result> { - let mut rows = conn - .query( - "SELECT name FROM sqlite_master WHERE type='table' AND name='fs_config'", - (), - ) - .await?; - - if rows.next().await?.is_none() { - return Ok(None); - } - - let mut rows = conn - .query("SELECT value FROM fs_config WHERE key = ?", (key,)) - .await?; - - if let Some(row) = rows.next().await? { - Ok(Some(row.get(0)?)) - } else { - Ok(None) - } -} diff --git a/sdk/typescript/.gitignore b/sdk/typescript/.gitignore deleted file mode 100644 index 3d887794..00000000 --- a/sdk/typescript/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -# Dependencies -node_modules/ - -# Build output -dist/ - -# TypeScript -*.tsbuildinfo - -# OS files -.DS_Store - -# IDE -.vscode/ -.idea/ - -# Test databases -*.db -*.db-shm -*.db-wal - -# AgentFS local databases -.agentfs/ - -# Logs -*.log -npm-debug.log* diff --git a/sdk/typescript/examples/filesystem/index.ts b/sdk/typescript/examples/filesystem/index.ts deleted file mode 100644 index f6fbc333..00000000 --- a/sdk/typescript/examples/filesystem/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { AgentFS } from "agentfs-sdk"; - -async function main() { - // Initialize AgentFS with persistent storage - const agentfs = await AgentFS.open({ id: "filesystem-demo" }); - - // Write a file - console.log("Writing file..."); - await agentfs.fs.writeFile("/documents/readme.txt", "Hello, world!"); - - // Read the file - console.log("\nReading file..."); - const content = await agentfs.fs.readFile("/documents/readme.txt"); - console.log("Content:", content); - - // Get file stats - console.log("\nFile stats:"); - const stats = await agentfs.fs.stat("/documents/readme.txt"); - console.log(" Inode:", stats.ino); - console.log(" Size:", stats.size, "bytes"); - console.log(" Mode:", stats.mode.toString(8)); - console.log(" Links:", stats.nlink); - console.log(" Is file:", stats.isFile()); - console.log(" Is directory:", stats.isDirectory()); - console.log(" Created:", new Date(stats.ctime * 1000).toISOString()); - console.log(" Modified:", new Date(stats.mtime * 1000).toISOString()); - - // List directory - console.log("\nListing /documents:"); - const files = await agentfs.fs.readdir("/documents"); - console.log(" Files:", files); - - // Write more files - await agentfs.fs.writeFile("/documents/notes.txt", "Some notes"); - await agentfs.fs.writeFile("/images/photo.jpg", "binary data here"); - - // List root - console.log("\nListing /:"); - const rootFiles = await agentfs.fs.readdir("/"); - console.log(" Directories:", rootFiles); - - // Check directory stats - console.log("\nDirectory stats for /documents:"); - const dirStats = await agentfs.fs.stat("/documents"); - console.log(" Is directory:", dirStats.isDirectory()); - console.log(" Mode:", dirStats.mode.toString(8)); -} - -main().catch(console.error); diff --git a/sdk/typescript/examples/filesystem/package.json b/sdk/typescript/examples/filesystem/package.json deleted file mode 100644 index 05d50fcc..00000000 --- a/sdk/typescript/examples/filesystem/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "filesystem-example", - "version": "1.0.0", - "private": true, - "type": "module", - "scripts": { - "start": "tsx index.ts" - }, - "dependencies": { - "agentfs-sdk": "file:../..", - "tsx": "^4.7.0" - } -} diff --git a/sdk/typescript/examples/kvstore/index.ts b/sdk/typescript/examples/kvstore/index.ts deleted file mode 100644 index 331826bd..00000000 --- a/sdk/typescript/examples/kvstore/index.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { AgentFS } from "agentfs-sdk"; - -async function main() { - // Initialize AgentFS with persistent storage - const agentfs = await AgentFS.open({ id: "kvstore-demo" }); - - console.log("=== KvStore Example ===\n"); - - // Example 1: Store and retrieve simple values - console.log("1. Storing simple values:"); - await agentfs.kv.set("username", "alice"); - await agentfs.kv.set("age", 30); - await agentfs.kv.set("active", true); - - const username = await agentfs.kv.get("username"); - const age = await agentfs.kv.get("age"); - const active = await agentfs.kv.get("active"); - - console.log(` Username: ${username}`); - console.log(` Age: ${age}`); - console.log(` Active: ${active}\n`); - - // Example 2: Store and retrieve objects - console.log("2. Storing complex objects:"); - const user = { - id: 1, - name: "Alice Johnson", - email: "alice@example.com", - preferences: { - theme: "dark", - notifications: true - } - }; - - await agentfs.kv.set("user:1", user); - const retrievedUser = await agentfs.kv.get("user:1"); - console.log(" Stored user:", JSON.stringify(retrievedUser, null, 2), "\n"); - - // Example 3: Store and retrieve arrays - console.log("3. Storing arrays:"); - const tags = ["typescript", "database", "ai", "agent"]; - await agentfs.kv.set("tags", tags); - const retrievedTags = await agentfs.kv.get("tags"); - console.log(` Tags: ${retrievedTags.join(", ")}\n`); - - // Example 4: Update existing values - console.log("4. Updating existing values:"); - console.log(` Age before update: ${await agentfs.kv.get("age")}`); - await agentfs.kv.set("age", 31); - console.log(` Age after update: ${await agentfs.kv.get("age")}\n`); - - // Example 5: Delete values - console.log("5. Deleting values:"); - console.log(` Username before delete: ${await agentfs.kv.get("username")}`); - await agentfs.kv.delete("username"); - console.log(` Username after delete: ${await agentfs.kv.get("username")}\n`); - - // Example 6: Handle non-existent keys - console.log("6. Retrieving non-existent keys:"); - const nonExistent = await agentfs.kv.get("does-not-exist"); - console.log(` Result: ${nonExistent}\n`); - - // Example 7: Use cases for AI agents - console.log("7. AI Agent use cases:"); - - // Session state - await agentfs.kv.set("session:current", { - conversationId: "conv-123", - userId: "user-456", - startTime: Date.now() - }); - - // Agent memory - await agentfs.kv.set("memory:user-preferences", { - language: "en", - responseStyle: "concise", - expertise: "intermediate" - }); - - // Task queue - await agentfs.kv.set("tasks:pending", [ - { id: 1, task: "Process document", priority: "high" }, - { id: 2, task: "Send notification", priority: "low" } - ]); - - console.log(" Session:", JSON.stringify(await agentfs.kv.get("session:current"), null, 2)); - console.log(" Memory:", JSON.stringify(await agentfs.kv.get("memory:user-preferences"), null, 2)); - console.log(" Tasks:", JSON.stringify(await agentfs.kv.get("tasks:pending"), null, 2)); - - console.log("\n=== Example Complete ==="); -} - -main().catch(console.error); diff --git a/sdk/typescript/examples/kvstore/package.json b/sdk/typescript/examples/kvstore/package.json deleted file mode 100644 index 516199a1..00000000 --- a/sdk/typescript/examples/kvstore/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "kvstore-example", - "version": "1.0.0", - "private": true, - "type": "module", - "scripts": { - "start": "tsx index.ts" - }, - "dependencies": { - "agentfs-sdk": "file:../..", - "tsx": "^4.7.0" - } -} diff --git a/sdk/typescript/examples/toolcalls/index.ts b/sdk/typescript/examples/toolcalls/index.ts deleted file mode 100644 index dc7478b5..00000000 --- a/sdk/typescript/examples/toolcalls/index.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { AgentFS } from '../../src'; - -async function main() { - // Create an agent with persistent storage - const agentfs = await AgentFS.open({ id: 'toolcalls-demo' }); - - console.log('=== Tool Call Tracking Example ===\n'); - - // Example 1: Successful tool call - console.log('1. Tracking a successful web search:'); - const startTime1 = Date.now() / 1000; - - // Simulate some work - await new Promise(resolve => setTimeout(resolve, 100)); - - const endTime1 = Date.now() / 1000; - const searchId = await agentfs.tools.record( - 'web_search', - startTime1, - endTime1, - { query: 'AI agents and LLMs', maxResults: 10 }, - { - results: [ - { title: 'Understanding AI Agents', url: 'https://example.com/1' }, - { title: 'LLM Best Practices', url: 'https://example.com/2' }, - ], - count: 2, - } - ); - console.log(` Recorded tool call with ID: ${searchId}\n`); - - // Example 2: Failed tool call - console.log('2. Tracking a failed API call:'); - const startTime2 = Date.now() / 1000; - - await new Promise(resolve => setTimeout(resolve, 50)); - - const endTime2 = Date.now() / 1000; - const apiId = await agentfs.tools.record( - 'api_call', - startTime2, - endTime2, - { endpoint: '/users', method: 'GET' }, - undefined, - 'Connection timeout after 30s' - ); - console.log(` Recorded failed call with ID: ${apiId}\n`); - - // Example 3: Multiple tool calls - console.log('3. Tracking multiple database queries:'); - for (let i = 0; i < 3; i++) { - const start = Date.now() / 1000; - await new Promise(resolve => setTimeout(resolve, 20)); - const end = Date.now() / 1000; - - await agentfs.tools.record( - 'database_query', - start, - end, - { sql: `SELECT * FROM users WHERE id = ${i + 1}` }, - { rows: 1 } - ); - } - console.log(' Created 3 database query records\n'); - - // Query tool calls by name - console.log('4. Querying tool calls by name:'); - const searches = await agentfs.tools.getByName('web_search'); - console.log(` Found ${searches.length} web search calls`); - if (searches.length > 0) { - const search = searches[0]; - console.log(` - Duration: ${search.duration_ms}ms`); - console.log(` - Parameters:`, JSON.stringify(search.parameters)); - console.log(` - Result:`, JSON.stringify(search.result)); - } - console.log(); - - // Get recent tool calls - console.log('5. Getting recent tool calls:'); - const oneMinuteAgo = Math.floor(Date.now() / 1000) - 60; - const recent = await agentfs.tools.getRecent(oneMinuteAgo); - console.log(` Found ${recent.length} calls in the last minute:`); - recent.forEach(tc => { - const status = tc.error ? 'failed' : 'success'; - console.log(` - ${tc.name} (${status})`); - }); - console.log(); - - // Get performance statistics - console.log('6. Performance statistics:'); - const stats = await agentfs.tools.getStats(); - console.log(' Tool Performance:'); - stats.forEach(stat => { - console.log(` - ${stat.name}:`); - console.log(` Total: ${stat.total_calls}, Success: ${stat.successful}, Failed: ${stat.failed}`); - console.log(` Avg Duration: ${stat.avg_duration_ms.toFixed(2)}ms`); - }); - - // Clean up - await agentfs.close(); - console.log('\n✓ Example completed successfully!'); -} - -main().catch(console.error); diff --git a/sdk/typescript/package-lock.json b/sdk/typescript/package-lock.json deleted file mode 100644 index 26ec7afd..00000000 --- a/sdk/typescript/package-lock.json +++ /dev/null @@ -1,2145 +0,0 @@ -{ - "name": "agentfs-sdk", - "version": "0.6.4", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "agentfs-sdk", - "version": "0.6.4", - "license": "MIT", - "dependencies": { - "@tursodatabase/database": "^0.4.0-pre.18", - "@tursodatabase/database-common": "^0.4.0-pre.18", - "buffer": "^6.0.3" - }, - "devDependencies": { - "@tursodatabase/database-wasm": "^0.4.0-pre.18", - "@tursodatabase/serverless": "^0.2.4", - "@types/node": "^20.0.0", - "@vitest/browser": "^4.0.16", - "@vitest/browser-playwright": "^4.0.16", - "@vitest/ui": "^4.0.1", - "just-bash": "^2.0.0", - "typescript": "^5.3.0", - "vitest": "^4.0.1" - }, - "peerDependencies": { - "@tursodatabase/serverless": "^0.2.4", - "just-bash": ">=2.0.0" - }, - "peerDependenciesMeta": { - "@tursodatabase/serverless": { - "optional": true - }, - "just-bash": { - "optional": true - } - } - }, - "node_modules/@borewit/text-codec": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", - "integrity": "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@mixmark-io/domino": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", - "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", - "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz", - "integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz", - "integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz", - "integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz", - "integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz", - "integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz", - "integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz", - "integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz", - "integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz", - "integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz", - "integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz", - "integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz", - "integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz", - "integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz", - "integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz", - "integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz", - "integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz", - "integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz", - "integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz", - "integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz", - "integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz", - "integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz", - "integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tursodatabase/database": { - "version": "0.4.0-pre.18", - "resolved": "https://registry.npmjs.org/@tursodatabase/database/-/database-0.4.0-pre.18.tgz", - "integrity": "sha512-NWmI4AJBK8HjRr5FIzafuNVRue60HKYPmrH6t4gYbGOGNg3sLpsMI/kxvyzdexcyFWZfKZNIhuLJZOgJdN32pQ==", - "license": "MIT", - "dependencies": { - "@tursodatabase/database-common": "^0.4.0-pre.18" - }, - "optionalDependencies": { - "@tursodatabase/database-darwin-arm64": "0.4.0-pre.18", - "@tursodatabase/database-linux-arm64-gnu": "0.4.0-pre.18", - "@tursodatabase/database-linux-x64-gnu": "0.4.0-pre.18", - "@tursodatabase/database-win32-x64-msvc": "0.4.0-pre.18" - } - }, - "node_modules/@tursodatabase/database-common": { - "version": "0.4.0-pre.18", - "resolved": "https://registry.npmjs.org/@tursodatabase/database-common/-/database-common-0.4.0-pre.18.tgz", - "integrity": "sha512-TLrdyC0sGjOgrMW0QgEXVFdMhiB8mQC6lRDJY1ks+i3ykZt+OR6yBAoNzb9AD9k4yMuFVFYgP8u8rdEZvSYr1Q==", - "license": "MIT" - }, - "node_modules/@tursodatabase/database-darwin-arm64": { - "version": "0.4.0-pre.18", - "resolved": "https://registry.npmjs.org/@tursodatabase/database-darwin-arm64/-/database-darwin-arm64-0.4.0-pre.18.tgz", - "integrity": "sha512-McbAv7lRak3bVbDDUCS0QvqmcXPgGGOP6hnGp9k77DjFiH1XNA0lCJ81dY9iMPvOk27ZDx9tQgu0ppW2VrNpBA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@tursodatabase/database-linux-arm64-gnu": { - "version": "0.4.0-pre.18", - "resolved": "https://registry.npmjs.org/@tursodatabase/database-linux-arm64-gnu/-/database-linux-arm64-gnu-0.4.0-pre.18.tgz", - "integrity": "sha512-dzWwksodFf0fvUY04s7Td8JKqAISqAf4irHa50esTTkq3RhirAwkvycbPrYL3o8KqSnZbxmk3H9sAY6aIA1Ajw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@tursodatabase/database-linux-x64-gnu": { - "version": "0.4.0-pre.18", - "resolved": "https://registry.npmjs.org/@tursodatabase/database-linux-x64-gnu/-/database-linux-x64-gnu-0.4.0-pre.18.tgz", - "integrity": "sha512-V3QfdsX5iXYD2ceSAme4vPJkmWxmFJMRz7BTjl+5X6gvJcLzWCskTFWC/f/xyzsBOnkKXtfSFf4vxrHS2AwNzA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@tursodatabase/database-wasm": { - "version": "0.4.0-pre.18", - "resolved": "https://registry.npmjs.org/@tursodatabase/database-wasm/-/database-wasm-0.4.0-pre.18.tgz", - "integrity": "sha512-57On7URuhcuW1zxvCpiAmyQmQASXahJoTp2O58P4akc3oUKDsLN8OPmS0Z1yJavm5QcRxgeKE8ua0kXttRy/Jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tursodatabase/database-common": "^0.4.0-pre.18", - "@tursodatabase/database-wasm-common": "^0.4.0-pre.18" - } - }, - "node_modules/@tursodatabase/database-wasm-common": { - "version": "0.4.0-pre.18", - "resolved": "https://registry.npmjs.org/@tursodatabase/database-wasm-common/-/database-wasm-common-0.4.0-pre.18.tgz", - "integrity": "sha512-sNaeEz6PVmT6VK2cfHrNDlp8Jw/T1WbnMzXCk9RKcCzTs9OdAkZ8x6Vb3Ddm6cp8r+uSEMhv+OA4Ym6LqPNf3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.5" - } - }, - "node_modules/@tursodatabase/database-win32-x64-msvc": { - "version": "0.4.0-pre.18", - "resolved": "https://registry.npmjs.org/@tursodatabase/database-win32-x64-msvc/-/database-win32-x64-msvc-0.4.0-pre.18.tgz", - "integrity": "sha512-pRB4N2NMQnqjAJJR1WWhbFDms7EpiS3I8mw5HlKGsc2vhthe5K3guKIWGcoxaK0og5D4cqW0bHf5rPLsWqO2dQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@tursodatabase/serverless": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@tursodatabase/serverless/-/serverless-0.2.4.tgz", - "integrity": "sha512-kqf3FINvTGx8N1iH/4e1Dkbr/om5q9qitshzvljtQXwZFQIsVP/mDE76mGg9U2dg/ElOZKFKtT1VXDgd39Dr1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.23.tgz", - "integrity": "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@vitest/browser": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.16.tgz", - "integrity": "sha512-t4toy8X/YTnjYEPoY0pbDBg3EvDPg1elCDrfc+VupPHwoN/5/FNQ8Z+xBYIaEnOE2vVEyKwqYBzZ9h9rJtZVcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/mocker": "4.0.16", - "@vitest/utils": "4.0.16", - "magic-string": "^0.30.21", - "pixelmatch": "7.1.0", - "pngjs": "^7.0.0", - "sirv": "^3.0.2", - "tinyrainbow": "^3.0.3", - "ws": "^8.18.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "4.0.16" - } - }, - "node_modules/@vitest/browser-playwright": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.16.tgz", - "integrity": "sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@vitest/browser": "4.0.16", - "@vitest/mocker": "4.0.16", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "playwright": "*", - "vitest": "4.0.16" - }, - "peerDependenciesMeta": { - "playwright": { - "optional": false - } - } - }, - "node_modules/@vitest/expect": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", - "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "chai": "^6.2.1", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", - "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.0.16", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", - "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", - "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.0.16", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", - "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.16", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", - "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/ui": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.16.tgz", - "integrity": "sha512-rkoPH+RqWopVxDnCBE/ysIdfQ2A7j1eDmW8tCxxrR9nnFBa9jKf86VgsSAzxBd1x+ny0GC4JgiD3SNfRHv3pOg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@vitest/utils": "4.0.16", - "fflate": "^0.8.2", - "flatted": "^3.3.3", - "pathe": "^2.0.3", - "sirv": "^3.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "4.0.16" - } - }, - "node_modules/@vitest/utils": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", - "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.16", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/chai": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", - "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/diff": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", - "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "dev": true, - "license": "MIT" - }, - "node_modules/file-type": { - "version": "21.3.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.0.tgz", - "integrity": "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/just-bash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/just-bash/-/just-bash-2.0.0.tgz", - "integrity": "sha512-dIxmIwoHMZysQfjOm5pLlpE75gb0QNMTzuhUSaip/YbSSM4x8jA77EdVEex2TH7pIE5PocApEv3WxavsnVQLEA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "diff": "^8.0.2", - "file-type": "^21.2.0", - "minimatch": "^10.1.1", - "sprintf-js": "^1.1.3", - "turndown": "^7.2.2" - }, - "bin": { - "just-bash": "dist/bin/just-bash.js", - "just-bash-shell": "dist/bin/shell/shell.js" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pixelmatch": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", - "integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==", - "dev": true, - "license": "ISC", - "dependencies": { - "pngjs": "^7.0.0" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" - } - }, - "node_modules/playwright": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", - "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.57.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", - "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/pngjs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", - "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.19.0" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/rollup": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz", - "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.5", - "@rollup/rollup-android-arm64": "4.53.5", - "@rollup/rollup-darwin-arm64": "4.53.5", - "@rollup/rollup-darwin-x64": "4.53.5", - "@rollup/rollup-freebsd-arm64": "4.53.5", - "@rollup/rollup-freebsd-x64": "4.53.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.5", - "@rollup/rollup-linux-arm-musleabihf": "4.53.5", - "@rollup/rollup-linux-arm64-gnu": "4.53.5", - "@rollup/rollup-linux-arm64-musl": "4.53.5", - "@rollup/rollup-linux-loong64-gnu": "4.53.5", - "@rollup/rollup-linux-ppc64-gnu": "4.53.5", - "@rollup/rollup-linux-riscv64-gnu": "4.53.5", - "@rollup/rollup-linux-riscv64-musl": "4.53.5", - "@rollup/rollup-linux-s390x-gnu": "4.53.5", - "@rollup/rollup-linux-x64-gnu": "4.53.5", - "@rollup/rollup-linux-x64-musl": "4.53.5", - "@rollup/rollup-openharmony-arm64": "4.53.5", - "@rollup/rollup-win32-arm64-msvc": "4.53.5", - "@rollup/rollup-win32-ia32-msvc": "4.53.5", - "@rollup/rollup-win32-x64-gnu": "4.53.5", - "@rollup/rollup-win32-x64-msvc": "4.53.5", - "fsevents": "~2.3.2" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, - "node_modules/strtok3": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.2.1", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/turndown": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.2.tgz", - "integrity": "sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mixmark-io/domino": "^2.2.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", - "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", - "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@vitest/expect": "4.0.16", - "@vitest/mocker": "4.0.16", - "@vitest/pretty-format": "4.0.16", - "@vitest/runner": "4.0.16", - "@vitest/snapshot": "4.0.16", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^3.10.0", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.16", - "@vitest/browser-preview": "4.0.16", - "@vitest/browser-webdriverio": "4.0.16", - "@vitest/ui": "4.0.16", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - } - } -} diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json deleted file mode 100644 index dcba9be0..00000000 --- a/sdk/typescript/package.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "name": "agentfs-sdk", - "version": "0.6.4", - "description": "AgentFS SDK", - "main": "dist/index_node.js", - "types": "dist/index_node.d.ts", - "packageManager": "npm@10.9.0", - "type": "module", - "scripts": { - "build": "tsc", - "watch": "tsc --watch", - "test": "vitest run", - "test:browser": "CI=1 vitest -c vitest.browser.config.ts --browser=chromium --run && CI=1 vitest -c vitest.browser.config.ts --browser=firefox --run", - "test:watch": "vitest watch", - "test:ui": "vitest --ui", - "test:coverage": "vitest run --coverage", - "prepublishOnly": "npm run build" - }, - "exports": { - ".": { - "node": "./dist/index_node.js", - "browser": "./dist/index_browser.js", - "default": "./dist/index_node.js" - }, - "./just-bash": { - "import": "./dist/integrations/just-bash/index.js", - "types": "./dist/integrations/just-bash/index.d.ts" - }, - "./cloudflare": { - "import": "./dist/integrations/cloudflare/index.js", - "types": "./dist/integrations/cloudflare/index.d.ts" - }, - "./serverless": { - "import": "./dist/integrations/serverless/index.js", - "types": "./dist/integrations/serverless/index.d.ts" - } - }, - "keywords": [ - "ai", - "agent", - "turso", - "sqlite", - "key-value", - "filesystem" - ], - "author": "", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/tursodatabase/agentfs" - }, - "devDependencies": { - "@tursodatabase/database-wasm": "^0.4.0-pre.18", - "@tursodatabase/serverless": "^0.2.4", - "@types/node": "^20.0.0", - "@vitest/browser": "^4.0.16", - "@vitest/browser-playwright": "^4.0.16", - "@vitest/ui": "^4.0.1", - "just-bash": "^2.0.0", - "typescript": "^5.3.0", - "vitest": "^4.0.1" - }, - "peerDependencies": { - "@tursodatabase/serverless": "^0.2.4", - "just-bash": ">=2.0.0" - }, - "peerDependenciesMeta": { - "just-bash": { - "optional": true - }, - "@tursodatabase/serverless": { - "optional": true - } - }, - "dependencies": { - "@tursodatabase/database": "^0.4.0-pre.18", - "@tursodatabase/database-common": "^0.4.0-pre.18", - "buffer": "^6.0.3" - }, - "files": [ - "dist" - ] -} diff --git a/sdk/typescript/src/agentfs.ts b/sdk/typescript/src/agentfs.ts deleted file mode 100644 index 07748efa..00000000 --- a/sdk/typescript/src/agentfs.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { DatabasePromise } from '@tursodatabase/database-common'; -import { KvStore } from './kvstore.js'; -import { AgentFS as Filesystem } from './filesystem/index.js'; -import { ToolCalls } from './toolcalls.js'; - -/** - * Configuration options for opening an AgentFS instance - */ -export interface AgentFSOptions { - /** - * Unique identifier for the agent. - * - If provided without `path`: Creates storage at `.agentfs/{id}.db` - * - If provided with `path`: Uses the specified path - */ - id?: string; - /** - * Explicit path to the database file. - * - If provided: Uses the specified path directly - * - Can be combined with `id` - */ - path?: string; -} - -export class AgentFSCore { - private db: DatabasePromise; - - public readonly kv: KvStore; - public readonly fs: Filesystem; - public readonly tools: ToolCalls; - - /** - * Private constructor - use AgentFS.open() instead - */ - protected constructor(db: DatabasePromise, kv: KvStore, fs: Filesystem, tools: ToolCalls) { - this.db = db; - this.kv = kv; - this.fs = fs; - this.tools = tools; - } - - /** - * Get the underlying Database instance - */ - getDatabase(): DatabasePromise { - return this.db; - } - - /** - * Close the database connection - */ - async close(): Promise { - await this.db.close(); - } -} diff --git a/sdk/typescript/src/errors.ts b/sdk/typescript/src/errors.ts deleted file mode 100644 index 9fdea885..00000000 --- a/sdk/typescript/src/errors.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * POSIX-style error codes for filesystem operations. - */ -export type FsErrorCode = - | 'ENOENT' // No such file or directory - | 'EEXIST' // File already exists - | 'EISDIR' // Is a directory (when file expected) - | 'ENOTDIR' // Not a directory (when directory expected) - | 'ENOTEMPTY' // Directory not empty - | 'EPERM' // Operation not permitted - | 'EINVAL' // Invalid argument - | 'ENOSYS'; // Function not implemented (use for symlinks) - -/** - * Filesystem syscall names for error reporting. - * rm, scandir and copyFile are not actual syscall but used for convenience - */ -export type FsSyscall = - | 'open' - | 'stat' - | 'mkdir' - | 'rmdir' - | 'rm' - | 'unlink' - | 'rename' - | 'scandir' - | 'copyfile' - | 'access'; - -export interface ErrnoException extends Error { - code?: FsErrorCode; - syscall?: FsSyscall; - path?: string; -} - -export function createFsError(params: { - code: FsErrorCode; - syscall: FsSyscall; - path?: string; - message?: string; -}): ErrnoException { - const { code, syscall, path, message } = params; - const base = message ?? code; - const suffix = - path !== undefined - ? ` '${path}'` - : ''; - const err = new Error(`${code}: ${base}, ${syscall}${suffix}`) as ErrnoException; - err.code = code; - err.syscall = syscall; - if (path !== undefined) err.path = path; - return err; -} diff --git a/sdk/typescript/src/filesystem/agentfs.ts b/sdk/typescript/src/filesystem/agentfs.ts deleted file mode 100644 index c83f414d..00000000 --- a/sdk/typescript/src/filesystem/agentfs.ts +++ /dev/null @@ -1,1201 +0,0 @@ -import type { DatabasePromise } from '@tursodatabase/database-common'; -import { createFsError, type FsSyscall } from '../errors.js'; -import { - assertInodeIsDirectory, - assertNotRoot, - assertNotSymlinkMode, - assertReadableExistingInode, - assertReaddirTargetInode, - assertUnlinkTargetInode, - assertWritableExistingInode, - getInodeModeOrThrow, - normalizeRmOptions, - throwENOENTUnlessForce, -} from '../guards.js'; -import { - S_IFMT, - S_IFREG, - S_IFDIR, - S_IFLNK, - DEFAULT_FILE_MODE, - DEFAULT_DIR_MODE, - createStats, - type Stats, - type DirEntry, - type FilesystemStats, - type FileHandle, - type FileSystem, -} from './interface.js'; - -const DEFAULT_CHUNK_SIZE = 4096; - -/** - * An open file handle for AgentFS. - */ -class AgentFSFile implements FileHandle { - private db: DatabasePromise; - private bufferCtor: BufferConstructor; - private ino: number; - private chunkSize: number; - - constructor(db: DatabasePromise, bufferCtor: BufferConstructor, ino: number, chunkSize: number) { - this.db = db; - this.bufferCtor = bufferCtor; - this.ino = ino; - this.chunkSize = chunkSize; - } - - async pread(offset: number, size: number): Promise { - const startChunk = Math.floor(offset / this.chunkSize); - const endChunk = Math.floor((offset + size - 1) / this.chunkSize); - - const stmt = this.db.prepare(` - SELECT chunk_index, data FROM fs_data - WHERE ino = ? AND chunk_index >= ? AND chunk_index <= ? - ORDER BY chunk_index ASC - `); - const rows = await stmt.all(this.ino, startChunk, endChunk) as { chunk_index: number; data: Buffer }[]; - - const buffers: Buffer[] = []; - let bytesCollected = 0; - const startOffsetInChunk = offset % this.chunkSize; - - for (const row of rows) { - const skip = buffers.length === 0 ? startOffsetInChunk : 0; - if (skip >= row.data.length) { - continue; - } - const remaining = size - bytesCollected; - const take = Math.min(row.data.length - skip, remaining); - buffers.push(row.data.subarray(skip, skip + take)); - bytesCollected += take; - } - - if (buffers.length === 0) { - return this.bufferCtor.alloc(0); - } - - return this.bufferCtor.concat(buffers); - } - - async pwrite(offset: number, data: Buffer): Promise { - if (data.length === 0) { - return; - } - - const sizeStmt = this.db.prepare('SELECT size FROM fs_inode WHERE ino = ?'); - const sizeRow = await sizeStmt.get(this.ino) as { size: number } | undefined; - const currentSize = sizeRow?.size ?? 0; - - if (offset > currentSize) { - const zeros = this.bufferCtor.alloc(offset - currentSize); - await this.writeDataAtOffset(currentSize, zeros); - } - - await this.writeDataAtOffset(offset, data); - - const newSize = Math.max(currentSize, offset + data.length); - const now = Math.floor(Date.now() / 1000); - const updateStmt = this.db.prepare('UPDATE fs_inode SET size = ?, mtime = ? WHERE ino = ?'); - await updateStmt.run(newSize, now, this.ino); - } - - private async writeDataAtOffset(offset: number, data: Buffer): Promise { - const startChunk = Math.floor(offset / this.chunkSize); - const endChunk = Math.floor((offset + data.length - 1) / this.chunkSize); - - for (let chunkIdx = startChunk; chunkIdx <= endChunk; chunkIdx++) { - const chunkStart = chunkIdx * this.chunkSize; - const chunkEnd = chunkStart + this.chunkSize; - - const dataStart = Math.max(0, chunkStart - offset); - const dataEnd = Math.min(data.length, chunkEnd - offset); - const writeOffset = Math.max(0, offset - chunkStart); - - const selectStmt = this.db.prepare('SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?'); - const existingRow = await selectStmt.get(this.ino, chunkIdx) as { data: Buffer } | undefined; - - let chunkData: Buffer; - if (existingRow) { - chunkData = this.bufferCtor.from(existingRow.data); - if (writeOffset + (dataEnd - dataStart) > chunkData.length) { - const newChunk = this.bufferCtor.alloc(writeOffset + (dataEnd - dataStart)); - chunkData.copy(newChunk); - chunkData = newChunk; - } - } else { - chunkData = this.bufferCtor.alloc(writeOffset + (dataEnd - dataStart)); - } - - data.copy(chunkData, writeOffset, dataStart, dataEnd); - - const upsertStmt = this.db.prepare(` - INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?) - ON CONFLICT(ino, chunk_index) DO UPDATE SET data = excluded.data - `); - await upsertStmt.run(this.ino, chunkIdx, chunkData); - } - } - - async truncate(newSize: number): Promise { - const sizeStmt = this.db.prepare('SELECT size FROM fs_inode WHERE ino = ?'); - const sizeRow = await sizeStmt.get(this.ino) as { size: number } | undefined; - const currentSize = sizeRow?.size ?? 0; - - await this.db.exec('BEGIN'); - try { - if (newSize === 0) { - const deleteStmt = this.db.prepare('DELETE FROM fs_data WHERE ino = ?'); - await deleteStmt.run(this.ino); - } else if (newSize < currentSize) { - const lastChunkIdx = Math.floor((newSize - 1) / this.chunkSize); - - const deleteStmt = this.db.prepare('DELETE FROM fs_data WHERE ino = ? AND chunk_index > ?'); - await deleteStmt.run(this.ino, lastChunkIdx); - - const offsetInChunk = newSize % this.chunkSize; - if (offsetInChunk > 0) { - const selectStmt = this.db.prepare('SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?'); - const row = await selectStmt.get(this.ino, lastChunkIdx) as { data: Buffer } | undefined; - - if (row && row.data.length > offsetInChunk) { - const truncatedChunk = row.data.subarray(0, offsetInChunk); - const updateStmt = this.db.prepare('UPDATE fs_data SET data = ? WHERE ino = ? AND chunk_index = ?'); - await updateStmt.run(truncatedChunk, this.ino, lastChunkIdx); - } - } - } - - const now = Math.floor(Date.now() / 1000); - const updateStmt = this.db.prepare('UPDATE fs_inode SET size = ?, mtime = ? WHERE ino = ?'); - await updateStmt.run(newSize, now, this.ino); - - await this.db.exec('COMMIT'); - } catch (e) { - await this.db.exec('ROLLBACK'); - throw e; - } - } - - async fsync(): Promise { - await this.db.exec('PRAGMA synchronous = FULL'); - await this.db.exec('PRAGMA wal_checkpoint(TRUNCATE)'); - } - - async fstat(): Promise { - const stmt = this.db.prepare(` - SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime - FROM fs_inode WHERE ino = ? - `); - const row = await stmt.get(this.ino) as { - ino: number; - mode: number; - nlink: number; - uid: number; - gid: number; - size: number; - atime: number; - mtime: number; - ctime: number; - } | undefined; - - if (!row) { - throw new Error('File handle refers to deleted inode'); - } - - return createStats(row); - } -} - -/** - * A filesystem backed by SQLite, implementing the FileSystem interface. - */ -export class AgentFS implements FileSystem { - private db: DatabasePromise; - private bufferCtor: BufferConstructor; - private rootIno: number = 1; - private chunkSize: number = DEFAULT_CHUNK_SIZE; - - private constructor(db: DatabasePromise, b: BufferConstructor) { - this.db = db; - this.bufferCtor = b; - } - - static async fromDatabase(db: DatabasePromise, b?: BufferConstructor): Promise { - const fs = new AgentFS(db, b ?? Buffer); - await fs.initialize(); - return fs; - } - - getChunkSize(): number { - return this.chunkSize; - } - - private async initialize(): Promise { - await this.db.exec(` - CREATE TABLE IF NOT EXISTS fs_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL - ) - `); - - await this.db.exec(` - CREATE TABLE IF NOT EXISTS fs_inode ( - ino INTEGER PRIMARY KEY AUTOINCREMENT, - mode INTEGER NOT NULL, - nlink INTEGER NOT NULL DEFAULT 0, - uid INTEGER NOT NULL DEFAULT 0, - gid INTEGER NOT NULL DEFAULT 0, - size INTEGER NOT NULL DEFAULT 0, - atime INTEGER NOT NULL, - mtime INTEGER NOT NULL, - ctime INTEGER NOT NULL, - rdev INTEGER NOT NULL DEFAULT 0, - atime_nsec INTEGER NOT NULL DEFAULT 0, - mtime_nsec INTEGER NOT NULL DEFAULT 0, - ctime_nsec INTEGER NOT NULL DEFAULT 0 - ) - `); - - await this.db.exec(` - CREATE TABLE IF NOT EXISTS fs_dentry ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - parent_ino INTEGER NOT NULL, - ino INTEGER NOT NULL, - UNIQUE(parent_ino, name) - ) - `); - - await this.db.exec(` - CREATE INDEX IF NOT EXISTS idx_fs_dentry_parent - ON fs_dentry(parent_ino, name) - `); - - await this.db.exec(` - CREATE TABLE IF NOT EXISTS fs_data ( - ino INTEGER NOT NULL, - chunk_index INTEGER NOT NULL, - data BLOB NOT NULL, - PRIMARY KEY (ino, chunk_index) - ) - `); - - await this.db.exec(` - CREATE TABLE IF NOT EXISTS fs_symlink ( - ino INTEGER PRIMARY KEY, - target TEXT NOT NULL - ) - `); - - this.chunkSize = await this.ensureRoot(); - } - - private async ensureRoot(): Promise { - const configStmt = this.db.prepare("SELECT value FROM fs_config WHERE key = 'chunk_size'"); - const config = await configStmt.get() as { value: string } | undefined; - - let chunkSize: number; - if (!config) { - const insertConfigStmt = this.db.prepare(` - INSERT INTO fs_config (key, value) VALUES ('chunk_size', ?) - `); - await insertConfigStmt.run(DEFAULT_CHUNK_SIZE.toString()); - chunkSize = DEFAULT_CHUNK_SIZE; - } else { - chunkSize = parseInt(config.value, 10) || DEFAULT_CHUNK_SIZE; - } - - // Set schema version (keep in sync with AGENTFS_SCHEMA_VERSION in sdk/rust/src/schema.rs) - const schemaVersionStmt = this.db.prepare(` - INSERT OR REPLACE INTO fs_config (key, value) VALUES ('schema_version', '0.4') - `); - await schemaVersionStmt.run(); - - const stmt = this.db.prepare('SELECT ino FROM fs_inode WHERE ino = ?'); - const root = await stmt.get(this.rootIno); - - if (!root) { - const now = Math.floor(Date.now() / 1000); - const insertStmt = this.db.prepare(` - INSERT INTO fs_inode (ino, mode, nlink, uid, gid, size, atime, mtime, ctime) - VALUES (?, ?, 1, 0, 0, 0, ?, ?, ?) - `); - await insertStmt.run(this.rootIno, DEFAULT_DIR_MODE, now, now, now); - } - - return chunkSize; - } - - private normalizePath(path: string): string { - const normalized = path.replace(/\/+$/, '') || '/'; - return normalized.startsWith('/') ? normalized : '/' + normalized; - } - - private splitPath(path: string): string[] { - const normalized = this.normalizePath(path); - if (normalized === '/') return []; - return normalized.split('/').filter(p => p); - } - - private async resolvePathOrThrow( - path: string, - syscall: FsSyscall - ): Promise<{ normalizedPath: string; ino: number }> { - const normalizedPath = this.normalizePath(path); - const ino = await this.resolvePath(normalizedPath); - if (ino === null) { - throw createFsError({ - code: 'ENOENT', - syscall, - path: normalizedPath, - message: 'no such file or directory', - }); - } - return { normalizedPath, ino }; - } - - private async resolvePath(path: string): Promise { - const normalized = this.normalizePath(path); - - if (normalized === '/') { - return this.rootIno; - } - - const parts = this.splitPath(normalized); - let currentIno = this.rootIno; - - for (const name of parts) { - const stmt = this.db.prepare(` - SELECT ino FROM fs_dentry - WHERE parent_ino = ? AND name = ? - `); - const result = await stmt.get(currentIno, name) as { ino: number } | undefined; - - if (!result) { - return null; - } - - currentIno = result.ino; - } - - return currentIno; - } - - private async resolveParent(path: string): Promise<{ parentIno: number; name: string } | null> { - const normalized = this.normalizePath(path); - - if (normalized === '/') { - return null; - } - - const parts = this.splitPath(normalized); - const name = parts[parts.length - 1]; - const parentPath = parts.length === 1 ? '/' : '/' + parts.slice(0, -1).join('/'); - - const parentIno = await this.resolvePath(parentPath); - - if (parentIno === null) { - return null; - } - - return { parentIno, name }; - } - - private async createInode(mode: number, uid: number = 0, gid: number = 0): Promise { - const now = Math.floor(Date.now() / 1000); - const stmt = this.db.prepare(` - INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime) - VALUES (?, ?, ?, 0, ?, ?, ?) - RETURNING ino - `); - const { ino } = await stmt.get(mode, uid, gid, now, now, now); - return Number(ino); - } - - private async createDentry(parentIno: number, name: string, ino: number): Promise { - const stmt = this.db.prepare(` - INSERT INTO fs_dentry (name, parent_ino, ino) - VALUES (?, ?, ?) - `); - await stmt.run(name, parentIno, ino); - - const updateStmt = this.db.prepare('UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?'); - await updateStmt.run(ino); - } - - private async ensureParentDirs(path: string): Promise { - const parts = this.splitPath(path); - parts.pop(); - - let currentIno = this.rootIno; - - for (const name of parts) { - const stmt = this.db.prepare(` - SELECT ino FROM fs_dentry - WHERE parent_ino = ? AND name = ? - `); - const result = await stmt.get(currentIno, name) as { ino: number } | undefined; - - if (!result) { - const dirIno = await this.createInode(DEFAULT_DIR_MODE); - await this.createDentry(currentIno, name, dirIno); - currentIno = dirIno; - } else { - await assertInodeIsDirectory(this.db, result.ino, 'open', this.normalizePath(path)); - currentIno = result.ino; - } - } - } - - private async getLinkCount(ino: number): Promise { - const stmt = this.db.prepare('SELECT nlink FROM fs_inode WHERE ino = ?'); - const result = await stmt.get(ino) as { nlink: number } | undefined; - return result?.nlink ?? 0; - } - - private async getInodeMode(ino: number): Promise { - const stmt = this.db.prepare('SELECT mode FROM fs_inode WHERE ino = ?'); - const row = await stmt.get(ino) as { mode: number } | undefined; - return row?.mode ?? null; - } - - // ==================== FileSystem Interface Implementation ==================== - - async writeFile( - path: string, - content: string | Buffer, - options?: BufferEncoding | { encoding?: BufferEncoding } - ): Promise { - await this.ensureParentDirs(path); - - const ino = await this.resolvePath(path); - - const encoding = typeof options === 'string' - ? options - : options?.encoding; - - const normalizedPath = this.normalizePath(path); - if (ino !== null) { - await assertWritableExistingInode(this.db, ino, 'open', normalizedPath); - await this.updateFileContent(ino, content, encoding); - } else { - const parent = await this.resolveParent(path); - if (!parent) { - throw createFsError({ - code: 'ENOENT', - syscall: 'open', - path: normalizedPath, - message: 'no such file or directory', - }); - } - - await assertInodeIsDirectory(this.db, parent.parentIno, 'open', normalizedPath); - - const fileIno = await this.createInode(DEFAULT_FILE_MODE); - await this.createDentry(parent.parentIno, parent.name, fileIno); - await this.updateFileContent(fileIno, content, encoding); - } - } - - private async updateFileContent( - ino: number, - content: string | Buffer, - encoding?: BufferEncoding - ): Promise { - const buffer = typeof content === 'string' - ? this.bufferCtor.from(content, encoding ?? 'utf8') - : content; - const now = Math.floor(Date.now() / 1000); - - const deleteStmt = this.db.prepare('DELETE FROM fs_data WHERE ino = ?'); - await deleteStmt.run(ino); - - if (buffer.length > 0) { - const stmt = this.db.prepare(` - INSERT INTO fs_data (ino, chunk_index, data) - VALUES (?, ?, ?) - `); - - let chunkIndex = 0; - for (let offset = 0; offset < buffer.length; offset += this.chunkSize) { - const chunk = buffer.subarray(offset, Math.min(offset + this.chunkSize, buffer.length)); - await stmt.run(ino, chunkIndex, chunk); - chunkIndex++; - } - } - - const updateStmt = this.db.prepare(` - UPDATE fs_inode - SET size = ?, mtime = ? - WHERE ino = ? - `); - await updateStmt.run(buffer.length, now, ino); - } - - async readFile(path: string): Promise; - async readFile(path: string, encoding: BufferEncoding): Promise; - async readFile(path: string, options: { encoding: BufferEncoding }): Promise; - async readFile( - path: string, - options?: BufferEncoding | { encoding?: BufferEncoding } - ): Promise { - const encoding = typeof options === 'string' - ? options - : options?.encoding; - - const { normalizedPath, ino } = await this.resolvePathOrThrow(path, 'open'); - - await assertReadableExistingInode(this.db, ino, 'open', normalizedPath); - - const stmt = this.db.prepare(` - SELECT data FROM fs_data - WHERE ino = ? - ORDER BY chunk_index ASC - `); - const rows = await stmt.all(ino) as { data: Buffer }[]; - - let combined: Buffer; - if (rows.length === 0) { - combined = this.bufferCtor.alloc(0); - } else { - const buffers = rows.map(row => row.data); - combined = this.bufferCtor.concat(buffers); - } - - const now = Math.floor(Date.now() / 1000); - const updateStmt = this.db.prepare('UPDATE fs_inode SET atime = ? WHERE ino = ?'); - await updateStmt.run(now, ino); - - if (encoding) { - return combined.toString(encoding); - } - return combined; - } - - async readdir(path: string): Promise { - const { normalizedPath, ino } = await this.resolvePathOrThrow(path, 'scandir'); - - await assertReaddirTargetInode(this.db, ino, normalizedPath); - - const stmt = this.db.prepare(` - SELECT name FROM fs_dentry - WHERE parent_ino = ? - ORDER BY name ASC - `); - const rows = await stmt.all(ino) as { name: string }[]; - - return rows.map(row => row.name); - } - - async readdirPlus(path: string): Promise { - const { normalizedPath, ino } = await this.resolvePathOrThrow(path, 'scandir'); - - await assertReaddirTargetInode(this.db, ino, normalizedPath); - - const stmt = this.db.prepare(` - SELECT d.name, i.ino, i.mode, i.nlink, i.uid, i.gid, i.size, i.atime, i.mtime, i.ctime - FROM fs_dentry d - JOIN fs_inode i ON d.ino = i.ino - WHERE d.parent_ino = ? - ORDER BY d.name ASC - `); - const rows = await stmt.all(ino) as { - name: string; - ino: number; - mode: number; - nlink: number; - uid: number; - gid: number; - size: number; - atime: number; - mtime: number; - ctime: number; - }[]; - - return rows.map(row => ({ - name: row.name, - stats: createStats({ - ino: row.ino, - mode: row.mode, - nlink: row.nlink, - uid: row.uid, - gid: row.gid, - size: row.size, - atime: row.atime, - mtime: row.mtime, - ctime: row.ctime, - }), - })); - } - - async stat(path: string): Promise { - const { normalizedPath, ino } = await this.resolvePathOrThrow(path, 'stat'); - - const stmt = this.db.prepare(` - SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime - FROM fs_inode - WHERE ino = ? - `); - const row = await stmt.get(ino) as { - ino: number; - mode: number; - nlink: number; - uid: number; - gid: number; - size: number; - atime: number; - mtime: number; - ctime: number; - } | undefined; - - if (!row) { - throw createFsError({ - code: 'ENOENT', - syscall: 'stat', - path: normalizedPath, - message: 'no such file or directory', - }); - } - - return createStats(row); - } - - async lstat(path: string): Promise { - // For now, lstat is the same as stat since we don't follow symlinks in stat yet - return this.stat(path); - } - - async mkdir(path: string): Promise { - const normalizedPath = this.normalizePath(path); - - const existing = await this.resolvePath(normalizedPath); - if (existing !== null) { - throw createFsError({ - code: 'EEXIST', - syscall: 'mkdir', - path: normalizedPath, - message: 'file already exists', - }); - } - - const parent = await this.resolveParent(normalizedPath); - if (!parent) { - throw createFsError({ - code: 'ENOENT', - syscall: 'mkdir', - path: normalizedPath, - message: 'no such file or directory', - }); - } - - await assertInodeIsDirectory(this.db, parent.parentIno, 'mkdir', normalizedPath); - - const dirIno = await this.createInode(DEFAULT_DIR_MODE); - try { - await this.createDentry(parent.parentIno, parent.name, dirIno); - } catch { - throw createFsError({ - code: 'EEXIST', - syscall: 'mkdir', - path: normalizedPath, - message: 'file already exists', - }); - } - } - - async rmdir(path: string): Promise { - const normalizedPath = this.normalizePath(path); - assertNotRoot(normalizedPath, 'rmdir'); - - const { ino } = await this.resolvePathOrThrow(normalizedPath, 'rmdir'); - - const mode = await getInodeModeOrThrow(this.db, ino, 'rmdir', normalizedPath); - assertNotSymlinkMode(mode, 'rmdir', normalizedPath); - if ((mode & S_IFMT) !== S_IFDIR) { - throw createFsError({ - code: 'ENOTDIR', - syscall: 'rmdir', - path: normalizedPath, - message: 'not a directory', - }); - } - - const stmt = this.db.prepare(` - SELECT 1 as one FROM fs_dentry - WHERE parent_ino = ? - LIMIT 1 - `); - const child = await stmt.get(ino) as { one: number } | undefined; - if (child) { - throw createFsError({ - code: 'ENOTEMPTY', - syscall: 'rmdir', - path: normalizedPath, - message: 'directory not empty', - }); - } - - const parent = await this.resolveParent(normalizedPath); - if (!parent) { - throw createFsError({ - code: 'EPERM', - syscall: 'rmdir', - path: normalizedPath, - message: 'operation not permitted', - }); - } - - await this.removeDentryAndMaybeInode(parent.parentIno, parent.name, ino); - } - - async unlink(path: string): Promise { - const normalizedPath = this.normalizePath(path); - assertNotRoot(normalizedPath, 'unlink'); - const { ino } = await this.resolvePathOrThrow(normalizedPath, 'unlink'); - - await assertUnlinkTargetInode(this.db, ino, normalizedPath); - - const parent = (await this.resolveParent(normalizedPath))!; - - const stmt = this.db.prepare(` - DELETE FROM fs_dentry - WHERE parent_ino = ? AND name = ? - `); - await stmt.run(parent.parentIno, parent.name); - - const decrementStmt = this.db.prepare('UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?'); - await decrementStmt.run(ino); - - const linkCount = await this.getLinkCount(ino); - if (linkCount === 0) { - const deleteInodeStmt = this.db.prepare('DELETE FROM fs_inode WHERE ino = ?'); - await deleteInodeStmt.run(ino); - - const deleteDataStmt = this.db.prepare('DELETE FROM fs_data WHERE ino = ?'); - await deleteDataStmt.run(ino); - } - } - - async rm( - path: string, - options?: { force?: boolean; recursive?: boolean } - ): Promise { - const normalizedPath = this.normalizePath(path); - const { force, recursive } = normalizeRmOptions(options); - assertNotRoot(normalizedPath, 'rm'); - - const ino = await this.resolvePath(normalizedPath); - if (ino === null) { - throwENOENTUnlessForce(normalizedPath, 'rm', force); - return; - } - - const mode = await getInodeModeOrThrow(this.db, ino, 'rm', normalizedPath); - assertNotSymlinkMode(mode, 'rm', normalizedPath); - - const parent = await this.resolveParent(normalizedPath); - if (!parent) { - throw createFsError({ - code: 'EPERM', - syscall: 'rm', - path: normalizedPath, - message: 'operation not permitted', - }); - } - - if ((mode & S_IFMT) === S_IFDIR) { - if (!recursive) { - throw createFsError({ - code: 'EISDIR', - syscall: 'rm', - path: normalizedPath, - message: 'illegal operation on a directory', - }); - } - - await this.rmDirContentsRecursive(ino); - await this.removeDentryAndMaybeInode(parent.parentIno, parent.name, ino); - return; - } - - await this.removeDentryAndMaybeInode(parent.parentIno, parent.name, ino); - } - - private async rmDirContentsRecursive(dirIno: number): Promise { - const stmt = this.db.prepare(` - SELECT name, ino FROM fs_dentry - WHERE parent_ino = ? - ORDER BY name ASC - `); - const children = await stmt.all(dirIno) as { name: string; ino: number }[]; - - for (const child of children) { - const mode = await this.getInodeMode(child.ino); - if (mode === null) { - continue; - } - - if ((mode & S_IFMT) === S_IFDIR) { - await this.rmDirContentsRecursive(child.ino); - await this.removeDentryAndMaybeInode(dirIno, child.name, child.ino); - } else { - assertNotSymlinkMode(mode, 'rm', ''); - await this.removeDentryAndMaybeInode(dirIno, child.name, child.ino); - } - } - } - - private async removeDentryAndMaybeInode(parentIno: number, name: string, ino: number): Promise { - const stmt = this.db.prepare(` - DELETE FROM fs_dentry - WHERE parent_ino = ? AND name = ? - `); - await stmt.run(parentIno, name); - - const decrementStmt = this.db.prepare('UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?'); - await decrementStmt.run(ino); - - const linkCount = await this.getLinkCount(ino); - if (linkCount === 0) { - const deleteInodeStmt = this.db.prepare('DELETE FROM fs_inode WHERE ino = ?'); - await deleteInodeStmt.run(ino); - - const deleteDataStmt = this.db.prepare('DELETE FROM fs_data WHERE ino = ?'); - await deleteDataStmt.run(ino); - - const deleteSymlinkStmt = this.db.prepare('DELETE FROM fs_symlink WHERE ino = ?'); - await deleteSymlinkStmt.run(ino); - } - } - - async rename(oldPath: string, newPath: string): Promise { - const oldNormalized = this.normalizePath(oldPath); - const newNormalized = this.normalizePath(newPath); - - if (oldNormalized === newNormalized) return; - - assertNotRoot(oldNormalized, 'rename'); - assertNotRoot(newNormalized, 'rename'); - - const oldParent = await this.resolveParent(oldNormalized); - if (!oldParent) { - throw createFsError({ - code: 'EPERM', - syscall: 'rename', - path: oldNormalized, - message: 'operation not permitted', - }); - } - - const newParent = await this.resolveParent(newNormalized); - if (!newParent) { - throw createFsError({ - code: 'ENOENT', - syscall: 'rename', - path: newNormalized, - message: 'no such file or directory', - }); - } - - await assertInodeIsDirectory(this.db, newParent.parentIno, 'rename', newNormalized); - - await this.db.exec('BEGIN'); - try { - const oldResolved = await this.resolvePathOrThrow(oldNormalized, 'rename'); - const oldIno = oldResolved.ino; - const oldMode = await getInodeModeOrThrow(this.db, oldIno, 'rename', oldNormalized); - assertNotSymlinkMode(oldMode, 'rename', oldNormalized); - const oldIsDir = (oldMode & S_IFMT) === S_IFDIR; - - if (oldIsDir && newNormalized.startsWith(oldNormalized + '/')) { - throw createFsError({ - code: 'EINVAL', - syscall: 'rename', - path: newNormalized, - message: 'invalid argument', - }); - } - - const newIno = await this.resolvePath(newNormalized); - if (newIno !== null) { - const newMode = await getInodeModeOrThrow(this.db, newIno, 'rename', newNormalized); - assertNotSymlinkMode(newMode, 'rename', newNormalized); - const newIsDir = (newMode & S_IFMT) === S_IFDIR; - - if (newIsDir && !oldIsDir) { - throw createFsError({ - code: 'EISDIR', - syscall: 'rename', - path: newNormalized, - message: 'illegal operation on a directory', - }); - } - if (!newIsDir && oldIsDir) { - throw createFsError({ - code: 'ENOTDIR', - syscall: 'rename', - path: newNormalized, - message: 'not a directory', - }); - } - - if (newIsDir) { - const stmt = this.db.prepare(` - SELECT 1 as one FROM fs_dentry - WHERE parent_ino = ? - LIMIT 1 - `); - const child = await stmt.get(newIno) as { one: number } | undefined; - if (child) { - throw createFsError({ - code: 'ENOTEMPTY', - syscall: 'rename', - path: newNormalized, - message: 'directory not empty', - }); - } - } - - await this.removeDentryAndMaybeInode(newParent.parentIno, newParent.name, newIno); - } - - const stmt = this.db.prepare(` - UPDATE fs_dentry - SET parent_ino = ?, name = ? - WHERE parent_ino = ? AND name = ? - `); - await stmt.run(newParent.parentIno, newParent.name, oldParent.parentIno, oldParent.name); - - const now = Math.floor(Date.now() / 1000); - const updateInodeCtimeStmt = this.db.prepare(` - UPDATE fs_inode - SET ctime = ? - WHERE ino = ? - `); - await updateInodeCtimeStmt.run(now, oldIno); - - const updateDirTimesStmt = this.db.prepare(` - UPDATE fs_inode - SET mtime = ?, ctime = ? - WHERE ino = ? - `); - await updateDirTimesStmt.run(now, now, oldParent.parentIno); - if (newParent.parentIno !== oldParent.parentIno) { - await updateDirTimesStmt.run(now, now, newParent.parentIno); - } - - await this.db.exec('COMMIT'); - } catch (e) { - await this.db.exec('ROLLBACK'); - throw e; - } - } - - async copyFile(src: string, dest: string): Promise { - const srcNormalized = this.normalizePath(src); - const destNormalized = this.normalizePath(dest); - - if (srcNormalized === destNormalized) { - throw createFsError({ - code: 'EINVAL', - syscall: 'copyfile', - path: destNormalized, - message: 'invalid argument', - }); - } - - const { ino: srcIno } = await this.resolvePathOrThrow(srcNormalized, 'copyfile'); - await assertReadableExistingInode(this.db, srcIno, 'copyfile', srcNormalized); - - const stmt = this.db.prepare(` - SELECT mode, uid, gid, size FROM fs_inode WHERE ino = ? - `); - const srcRow = await stmt.get(srcIno) as - | { mode: number; uid: number; gid: number; size: number } - | undefined; - if (!srcRow) { - throw createFsError({ - code: 'ENOENT', - syscall: 'copyfile', - path: srcNormalized, - message: 'no such file or directory', - }); - } - - const destParent = await this.resolveParent(destNormalized); - if (!destParent) { - throw createFsError({ - code: 'ENOENT', - syscall: 'copyfile', - path: destNormalized, - message: 'no such file or directory', - }); - } - await assertInodeIsDirectory(this.db, destParent.parentIno, 'copyfile', destNormalized); - - await this.db.exec('BEGIN'); - try { - const now = Math.floor(Date.now() / 1000); - - const destIno = await this.resolvePath(destNormalized); - if (destIno !== null) { - const destMode = await getInodeModeOrThrow(this.db, destIno, 'copyfile', destNormalized); - assertNotSymlinkMode(destMode, 'copyfile', destNormalized); - if ((destMode & S_IFMT) === S_IFDIR) { - throw createFsError({ - code: 'EISDIR', - syscall: 'copyfile', - path: destNormalized, - message: 'illegal operation on a directory', - }); - } - - const deleteStmt = this.db.prepare('DELETE FROM fs_data WHERE ino = ?'); - await deleteStmt.run(destIno); - - const copyStmt = this.db.prepare(` - INSERT INTO fs_data (ino, chunk_index, data) - SELECT ?, chunk_index, data - FROM fs_data - WHERE ino = ? - ORDER BY chunk_index ASC - `); - await copyStmt.run(destIno, srcIno); - - const updateStmt = this.db.prepare(` - UPDATE fs_inode - SET mode = ?, uid = ?, gid = ?, size = ?, mtime = ?, ctime = ? - WHERE ino = ? - `); - await updateStmt.run(srcRow.mode, srcRow.uid, srcRow.gid, srcRow.size, now, now, destIno); - } else { - const destInoCreated = await this.createInode(srcRow.mode, srcRow.uid, srcRow.gid); - await this.createDentry(destParent.parentIno, destParent.name, destInoCreated); - - const copyStmt = this.db.prepare(` - INSERT INTO fs_data (ino, chunk_index, data) - SELECT ?, chunk_index, data - FROM fs_data - WHERE ino = ? - ORDER BY chunk_index ASC - `); - await copyStmt.run(destInoCreated, srcIno); - - const updateStmt = this.db.prepare(` - UPDATE fs_inode - SET size = ?, mtime = ?, ctime = ? - WHERE ino = ? - `); - await updateStmt.run(srcRow.size, now, now, destInoCreated); - } - - await this.db.exec('COMMIT'); - } catch (e) { - await this.db.exec('ROLLBACK'); - throw e; - } - } - - async symlink(target: string, linkpath: string): Promise { - const normalizedLinkpath = this.normalizePath(linkpath); - - const existing = await this.resolvePath(normalizedLinkpath); - if (existing !== null) { - throw createFsError({ - code: 'EEXIST', - syscall: 'open', - path: normalizedLinkpath, - message: 'file already exists', - }); - } - - const parent = await this.resolveParent(normalizedLinkpath); - if (!parent) { - throw createFsError({ - code: 'ENOENT', - syscall: 'open', - path: normalizedLinkpath, - message: 'no such file or directory', - }); - } - - await assertInodeIsDirectory(this.db, parent.parentIno, 'open', normalizedLinkpath); - - const mode = S_IFLNK | 0o777; - const symlinkIno = await this.createInode(mode); - await this.createDentry(parent.parentIno, parent.name, symlinkIno); - - const stmt = this.db.prepare('INSERT INTO fs_symlink (ino, target) VALUES (?, ?)'); - await stmt.run(symlinkIno, target); - - const updateStmt = this.db.prepare('UPDATE fs_inode SET size = ? WHERE ino = ?'); - await updateStmt.run(target.length, symlinkIno); - } - - async readlink(path: string): Promise { - const { normalizedPath, ino } = await this.resolvePathOrThrow(path, 'open'); - - const mode = await this.getInodeMode(ino); - if (mode === null || (mode & S_IFMT) !== S_IFLNK) { - throw createFsError({ - code: 'EINVAL', - syscall: 'open', - path: normalizedPath, - message: 'invalid argument', - }); - } - - const stmt = this.db.prepare('SELECT target FROM fs_symlink WHERE ino = ?'); - const row = await stmt.get(ino) as { target: string } | undefined; - - if (!row) { - throw createFsError({ - code: 'ENOENT', - syscall: 'open', - path: normalizedPath, - message: 'no such file or directory', - }); - } - - return row.target; - } - - async access(path: string): Promise { - const normalizedPath = this.normalizePath(path); - const ino = await this.resolvePath(normalizedPath); - if (ino === null) { - throw createFsError({ - code: 'ENOENT', - syscall: 'access', - path: normalizedPath, - message: 'no such file or directory', - }); - } - } - - async statfs(): Promise { - const inodeStmt = this.db.prepare('SELECT COUNT(*) as count FROM fs_inode'); - const inodeRow = await inodeStmt.get() as { count: number }; - - const bytesStmt = this.db.prepare('SELECT COALESCE(SUM(LENGTH(data)), 0) as total FROM fs_data'); - const bytesRow = await bytesStmt.get() as { total: number }; - - return { - inodes: inodeRow.count, - bytesUsed: bytesRow.total, - }; - } - - async open(path: string): Promise { - const { normalizedPath, ino } = await this.resolvePathOrThrow(path, 'open'); - await assertReadableExistingInode(this.db, ino, 'open', normalizedPath); - - return new AgentFSFile(this.db, this.bufferCtor, ino, this.chunkSize); - } - - // Legacy alias - async deleteFile(path: string): Promise { - return await this.unlink(path); - } -} diff --git a/sdk/typescript/src/filesystem/index.ts b/sdk/typescript/src/filesystem/index.ts deleted file mode 100644 index 990079fc..00000000 --- a/sdk/typescript/src/filesystem/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -export { - S_IFMT, - S_IFREG, - S_IFDIR, - S_IFLNK, - DEFAULT_FILE_MODE, - DEFAULT_DIR_MODE, - createStats, -} from './interface.js'; - -export type { - Stats, - StatsData, - DirEntry, - FilesystemStats, - FileHandle, - FileSystem, -} from './interface.js'; - -export { AgentFS } from './agentfs.js'; diff --git a/sdk/typescript/src/filesystem/interface.ts b/sdk/typescript/src/filesystem/interface.ts deleted file mode 100644 index f3c52d04..00000000 --- a/sdk/typescript/src/filesystem/interface.ts +++ /dev/null @@ -1,233 +0,0 @@ -// File types for mode field -export const S_IFMT = 0o170000; // File type mask -export const S_IFREG = 0o100000; // Regular file -export const S_IFDIR = 0o040000; // Directory -export const S_IFLNK = 0o120000; // Symbolic link - -// Default permissions -export const DEFAULT_FILE_MODE = S_IFREG | 0o644; // Regular file, rw-r--r-- -export const DEFAULT_DIR_MODE = S_IFDIR | 0o755; // Directory, rwxr-xr-x - -/** - * File statistics (Node.js compatible) - */ -export interface Stats { - ino: number; - mode: number; - nlink: number; - uid: number; - gid: number; - size: number; - atime: number; - mtime: number; - ctime: number; - isFile(): boolean; - isDirectory(): boolean; - isSymbolicLink(): boolean; -} - -/** - * Plain stats data without methods (for creating Stats objects) - */ -export interface StatsData { - ino: number; - mode: number; - nlink: number; - uid: number; - gid: number; - size: number; - atime: number; - mtime: number; - ctime: number; -} - -/** - * Create a Stats object from raw data - */ -export function createStats(data: StatsData): Stats { - return { - ...data, - isFile: () => (data.mode & S_IFMT) === S_IFREG, - isDirectory: () => (data.mode & S_IFMT) === S_IFDIR, - isSymbolicLink: () => (data.mode & S_IFMT) === S_IFLNK, - }; -} - -/** - * Directory entry with full statistics - */ -export interface DirEntry { - /** Entry name (without path) */ - name: string; - /** Full statistics for this entry */ - stats: Stats; -} - -/** - * Filesystem statistics - */ -export interface FilesystemStats { - /** Total number of inodes (files, directories, symlinks) */ - inodes: number; - /** Total bytes used by file contents */ - bytesUsed: number; -} - -/** - * An open file handle for performing I/O operations. - * Similar to Node.js FileHandle from fs/promises. - */ -export interface FileHandle { - /** - * Read from the file at the given offset. - */ - pread(offset: number, size: number): Promise; - - /** - * Write to the file at the given offset. - */ - pwrite(offset: number, data: Buffer): Promise; - - /** - * Truncate the file to the specified size. - */ - truncate(size: number): Promise; - - /** - * Synchronize file data to persistent storage. - */ - fsync(): Promise; - - /** - * Get file statistics. - */ - fstat(): Promise; -} - -/** - * FileSystem interface following Node.js fs/promises API conventions. - * - * This interface abstracts over different filesystem backends, - * allowing implementations like AgentFS (SQLite-backed), HostFS (native filesystem), - * and OverlayFS (layered filesystem). - * - * Methods throw errors on failure (ENOENT, EEXIST, etc.) like Node.js fs/promises. - */ -export interface FileSystem { - /** - * Get file statistics. - * @throws {ErrnoException} ENOENT if path does not exist - */ - stat(path: string): Promise; - - /** - * Get file statistics without following symlinks. - * @throws {ErrnoException} ENOENT if path does not exist - */ - lstat(path: string): Promise; - - /** - * Read entire file contents. - * @throws {ErrnoException} ENOENT if file does not exist - * @throws {ErrnoException} EISDIR if path is a directory - */ - readFile(path: string): Promise; - readFile(path: string, encoding: BufferEncoding): Promise; - readFile(path: string, options: { encoding: BufferEncoding }): Promise; - readFile(path: string, options?: BufferEncoding | { encoding?: BufferEncoding }): Promise; - - /** - * Write data to a file (creates or overwrites). - * Creates parent directories if they don't exist. - */ - writeFile( - path: string, - data: string | Buffer, - options?: BufferEncoding | { encoding?: BufferEncoding } - ): Promise; - - /** - * List directory contents. - * @throws {ErrnoException} ENOENT if directory does not exist - * @throws {ErrnoException} ENOTDIR if path is not a directory - */ - readdir(path: string): Promise; - - /** - * List directory contents with full statistics for each entry. - * Optimized version that avoids N+1 queries. - * @throws {ErrnoException} ENOENT if directory does not exist - * @throws {ErrnoException} ENOTDIR if path is not a directory - */ - readdirPlus(path: string): Promise; - - /** - * Create a directory. - * @throws {ErrnoException} EEXIST if path already exists - * @throws {ErrnoException} ENOENT if parent does not exist - */ - mkdir(path: string): Promise; - - /** - * Remove an empty directory. - * @throws {ErrnoException} ENOENT if path does not exist - * @throws {ErrnoException} ENOTEMPTY if directory is not empty - * @throws {ErrnoException} ENOTDIR if path is not a directory - */ - rmdir(path: string): Promise; - - /** - * Remove a file. - * @throws {ErrnoException} ENOENT if path does not exist - * @throws {ErrnoException} EISDIR if path is a directory - */ - unlink(path: string): Promise; - - /** - * Remove a file or directory. - */ - rm(path: string, options?: { force?: boolean; recursive?: boolean }): Promise; - - /** - * Rename/move a file or directory. - * @throws {ErrnoException} ENOENT if source does not exist - */ - rename(oldPath: string, newPath: string): Promise; - - /** - * Copy a file. - * @throws {ErrnoException} ENOENT if source does not exist - * @throws {ErrnoException} EISDIR if source or dest is a directory - */ - copyFile(src: string, dest: string): Promise; - - /** - * Create a symbolic link. - * @throws {ErrnoException} EEXIST if linkpath already exists - */ - symlink(target: string, linkpath: string): Promise; - - /** - * Read the target of a symbolic link. - * @throws {ErrnoException} ENOENT if path does not exist - * @throws {ErrnoException} EINVAL if path is not a symlink - */ - readlink(path: string): Promise; - - /** - * Test file access (existence check). - * @throws {ErrnoException} ENOENT if path does not exist - */ - access(path: string): Promise; - - /** - * Get filesystem statistics. - */ - statfs(): Promise; - - /** - * Open a file and return a file handle for I/O operations. - * @throws {ErrnoException} ENOENT if file does not exist - */ - open(path: string): Promise; -} diff --git a/sdk/typescript/src/guards.ts b/sdk/typescript/src/guards.ts deleted file mode 100644 index baafcdd5..00000000 --- a/sdk/typescript/src/guards.ts +++ /dev/null @@ -1,194 +0,0 @@ -import type { DatabasePromise } from '@tursodatabase/database-common'; -import { createFsError, type FsSyscall } from './errors.js'; -import { S_IFDIR, S_IFLNK, S_IFMT } from './filesystem/interface.js'; - -async function getInodeMode(db: DatabasePromise, ino: number): Promise { - const stmt = db.prepare('SELECT mode FROM fs_inode WHERE ino = ?'); - const row = await stmt.get(ino) as { mode: number } | undefined; - return row?.mode ?? null; -} - -function isDirMode(mode: number): boolean { - return (mode & S_IFMT) === S_IFDIR; -} - -export async function getInodeModeOrThrow( - db: DatabasePromise, - ino: number, - syscall: FsSyscall, - path: string -): Promise { - const mode = await getInodeMode(db, ino); - if (mode === null) { - throw createFsError({ - code: 'ENOENT', - syscall, - path, - message: 'no such file or directory', - }); - } - return mode; -} - -export function assertNotRoot(path: string, syscall: FsSyscall): void { - if (path === '/') { - throw createFsError({ - code: 'EPERM', - syscall, - path, - message: 'operation not permitted on root directory', - }); - } -} - -export function normalizeRmOptions(options?: { force?: boolean; recursive?: boolean }): { - force: boolean; - recursive: boolean; -} { - return { - force: options?.force === true, - recursive: options?.recursive === true, - }; -} - -export function throwENOENTUnlessForce(path: string, syscall: FsSyscall, force: boolean): void { - if (force) return; - throw createFsError({ - code: 'ENOENT', - syscall, - path, - message: 'no such file or directory', - }); -} - -export function assertNotSymlinkMode(mode: number, syscall: FsSyscall, path: string): void { - if ((mode & S_IFMT) === S_IFLNK) { - throw createFsError({ - code: 'ENOSYS', - syscall, - path, - message: 'symbolic links not supported yet', - }); - } -} - -async function assertExistingNonDirNonSymlinkInode( - db: DatabasePromise, - ino: number, - syscall: FsSyscall, - fullPathForError: string -): Promise { - const mode = await getInodeMode(db, ino); - if (mode === null) { - throw createFsError({ - code: 'ENOENT', - syscall, - path: fullPathForError, - message: 'no such file or directory', - }); - } - if (isDirMode(mode)) { - throw createFsError({ - code: 'EISDIR', - syscall, - path: fullPathForError, - message: 'illegal operation on a directory', - }); - } - assertNotSymlinkMode(mode, syscall, fullPathForError); -} - -export async function assertInodeIsDirectory( - db: DatabasePromise, - ino: number, - syscall: FsSyscall, - fullPathForError: string -): Promise { - const mode = await getInodeMode(db, ino); - if (mode === null) { - throw createFsError({ - code: 'ENOENT', - syscall, - path: fullPathForError, - message: 'no such file or directory', - }); - } - if (!isDirMode(mode)) { - throw createFsError({ - code: 'ENOTDIR', - syscall, - path: fullPathForError, - message: 'not a directory', - }); - } -} - -export async function assertWritableExistingInode( - db: DatabasePromise, - ino: number, - syscall: FsSyscall, - fullPathForError: string -): Promise { - await assertExistingNonDirNonSymlinkInode(db, ino, syscall, fullPathForError); -} - -export async function assertReadableExistingInode( - db: DatabasePromise, - ino: number, - syscall: FsSyscall, - fullPathForError: string -): Promise { - await assertExistingNonDirNonSymlinkInode(db, ino, syscall, fullPathForError); -} - -export async function assertReaddirTargetInode( - db: DatabasePromise, - ino: number, - fullPathForError: string -): Promise { - const syscall = 'scandir'; - const mode = await getInodeMode(db, ino); - if (mode === null) { - throw createFsError({ - code: 'ENOENT', - syscall, - path: fullPathForError, - message: 'no such file or directory', - }); - } - assertNotSymlinkMode(mode, syscall, fullPathForError); - if (!isDirMode(mode)) { - throw createFsError({ - code: 'ENOTDIR', - syscall, - path: fullPathForError, - message: 'not a directory', - }); - } -} - -export async function assertUnlinkTargetInode( - db: DatabasePromise, - ino: number, - fullPathForError: string -): Promise { - const syscall = 'unlink'; - const mode = await getInodeMode(db, ino); - if (mode === null) { - throw createFsError({ - code: 'ENOENT', - syscall, - path: fullPathForError, - message: 'no such file or directory', - }); - } - if (isDirMode(mode)) { - throw createFsError({ - code: 'EISDIR', - syscall, - path: fullPathForError, - message: 'illegal operation on a directory', - }); - } - assertNotSymlinkMode(mode, syscall, fullPathForError); -} \ No newline at end of file diff --git a/sdk/typescript/src/index_browser.ts b/sdk/typescript/src/index_browser.ts deleted file mode 100644 index 48cd3859..00000000 --- a/sdk/typescript/src/index_browser.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { AgentFSCore } from "./agentfs.js"; -import { DatabasePromise } from "@tursodatabase/database-common"; -import { KvStore } from "./kvstore.js"; -import { AgentFS as AgentFSImpl } from "./filesystem/index.js"; -import { ToolCalls } from "./toolcalls.js"; -import { Buffer } from "buffer"; - -export class AgentFS extends AgentFSCore { - static async openWith(db: DatabasePromise): Promise { - const [kv, fs, tools] = await Promise.all([ - KvStore.fromDatabase(db), - AgentFSImpl.fromDatabase(db, Buffer), - ToolCalls.fromDatabase(db), - ]); - return new AgentFS(db, kv, fs, tools); - } -} - -export { AgentFSOptions } from './agentfs.js'; -export { KvStore } from './kvstore.js'; -export { AgentFS as Filesystem } from './filesystem/index.js'; -export type { Stats, DirEntry, FilesystemStats, FileHandle, FileSystem } from './filesystem/index.js'; -export { ToolCalls } from './toolcalls.js'; -export type { ToolCall, ToolCallStats } from './toolcalls.js'; \ No newline at end of file diff --git a/sdk/typescript/src/index_node.ts b/sdk/typescript/src/index_node.ts deleted file mode 100644 index 51769005..00000000 --- a/sdk/typescript/src/index_node.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Database } from "@tursodatabase/database"; -import { existsSync, mkdirSync } from "fs"; -import { AgentFSCore, AgentFSOptions } from "./agentfs.js"; -import { DatabasePromise } from "@tursodatabase/database-common"; -import { KvStore } from "./kvstore.js"; -import { AgentFS as Filesystem } from "./filesystem/index.js"; -import { ToolCalls } from "./toolcalls.js"; - -export class AgentFS extends AgentFSCore { - /** - * Open an agent filesystem - * @param options Configuration options (id and/or path required) - * @returns Fully initialized AgentFS instance - * @example - * ```typescript - * // Using id (creates .agentfs/my-agent.db) - * const agent = await AgentFS.open({ id: 'my-agent' }); - * - * // Using id with custom path - * const agent = await AgentFS.open({ id: 'my-agent', path: './data/mydb.db' }); - * - * // Using path only - * const agent = await AgentFS.open({ path: './data/mydb.db' }); - * ``` - */ - static async open(options: AgentFSOptions): Promise { - const { id, path } = options; - - // Require at least id or path - if (!id && !path) { - throw new Error("AgentFS.open() requires at least 'id' or 'path'."); - } - - // Validate agent ID if provided - if (id && !/^[a-zA-Z0-9_-]+$/.test(id)) { - throw new Error( - 'Agent ID must contain only alphanumeric characters, hyphens, and underscores' - ); - } - - // Determine database path: explicit path takes precedence, otherwise use id-based path - let dbPath: string; - if (path) { - dbPath = path; - } else { - // id is guaranteed to be defined here (we checked !id && !path above) - const dir = '.agentfs'; - if (!existsSync(dir)) { - mkdirSync(dir, { recursive: true }); - } - dbPath = `${dir}/${id}.db`; - } - - const db = new Database(dbPath); - - // Connect to the database to ensure it's created - await db.connect(); - - return await this.openWith(db); - } - - static async openWith(db: DatabasePromise): Promise { - const [kv, fs, tools] = await Promise.all([ - KvStore.fromDatabase(db), - Filesystem.fromDatabase(db), - ToolCalls.fromDatabase(db), - ]); - return new AgentFS(db, kv, fs, tools); - } -} - -export { AgentFSOptions } from './agentfs.js'; -export { KvStore } from './kvstore.js'; -export { AgentFS as Filesystem } from './filesystem/index.js'; -export type { Stats, DirEntry, FilesystemStats, FileHandle, FileSystem } from './filesystem/index.js'; -export { ToolCalls } from './toolcalls.js'; -export type { ToolCall, ToolCallStats } from './toolcalls.js'; diff --git a/sdk/typescript/src/integrations/cloudflare/agentfs.ts b/sdk/typescript/src/integrations/cloudflare/agentfs.ts deleted file mode 100644 index c557a8a1..00000000 --- a/sdk/typescript/src/integrations/cloudflare/agentfs.ts +++ /dev/null @@ -1,1381 +0,0 @@ -/** - * AgentFS - FileSystem implementation using Cloudflare Durable Objects SQLite - * - * This implementation uses Cloudflare's Durable Objects SQLite storage API, - * allowing AgentFS to run on Cloudflare's edge platform. - * - * @see https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/ - */ - -import { - S_IFMT, - S_IFREG, - S_IFDIR, - S_IFLNK, - DEFAULT_FILE_MODE, - DEFAULT_DIR_MODE, - createStats, - type Stats, - type DirEntry, - type FilesystemStats, - type FileHandle, - type FileSystem, -} from '../../filesystem/interface.js'; - -const DEFAULT_CHUNK_SIZE = 4096; - -/** - * Cloudflare Durable Objects SqlStorage cursor interface - */ -interface SqlStorageCursor> { - toArray(): T[]; - one(): T; - raw(): IterableIterator; - readonly columnNames: string[]; - readonly rowsRead: number; - readonly rowsWritten: number; - next(): { done: boolean; value?: T }; - [Symbol.iterator](): IterableIterator; -} - -/** - * Cloudflare Durable Objects SqlStorage interface - */ -interface SqlStorage { - exec>(query: string, ...bindings: unknown[]): SqlStorageCursor; - readonly databaseSize: number; -} - -/** - * Cloudflare Durable Objects Storage interface (subset we need) - */ -export interface CloudflareStorage { - readonly sql: SqlStorage; - transactionSync(callback: () => T): T; -} - -/** - * Error codes for filesystem operations - */ -type FsErrorCode = 'ENOENT' | 'EEXIST' | 'EISDIR' | 'ENOTDIR' | 'ENOTEMPTY' | 'EPERM' | 'EINVAL'; - -interface FsError extends Error { - code: FsErrorCode; - syscall: string; - path: string; -} - -function createFsError(opts: { code: FsErrorCode; syscall: string; path: string; message: string }): FsError { - const err = new Error(`${opts.code}: ${opts.message}, ${opts.syscall} '${opts.path}'`) as FsError; - err.code = opts.code; - err.syscall = opts.syscall; - err.path = opts.path; - return err; -} - -/** - * An open file handle for AgentFS. - */ -class AgentFSFile implements FileHandle { - private storage: CloudflareStorage; - private ino: number; - private chunkSize: number; - - constructor(storage: CloudflareStorage, ino: number, chunkSize: number) { - this.storage = storage; - this.ino = ino; - this.chunkSize = chunkSize; - } - - async pread(offset: number, size: number): Promise { - const startChunk = Math.floor(offset / this.chunkSize); - const endChunk = Math.floor((offset + size - 1) / this.chunkSize); - - const rows = this.storage.sql.exec<{ chunk_index: number; data: ArrayBuffer }>( - `SELECT chunk_index, data FROM fs_data - WHERE ino = ? AND chunk_index >= ? AND chunk_index <= ? - ORDER BY chunk_index ASC`, - this.ino, startChunk, endChunk - ).toArray(); - - const buffers: Buffer[] = []; - let bytesCollected = 0; - const startOffsetInChunk = offset % this.chunkSize; - - for (const row of rows) { - const data = Buffer.from(row.data); - const skip = buffers.length === 0 ? startOffsetInChunk : 0; - if (skip >= data.length) { - continue; - } - const remaining = size - bytesCollected; - const take = Math.min(data.length - skip, remaining); - buffers.push(data.subarray(skip, skip + take)); - bytesCollected += take; - } - - if (buffers.length === 0) { - return Buffer.alloc(0); - } - - return Buffer.concat(buffers); - } - - async pwrite(offset: number, data: Buffer): Promise { - if (data.length === 0) { - return; - } - - this.storage.transactionSync(() => { - const sizeRow = this.storage.sql.exec<{ size: number }>( - 'SELECT size FROM fs_inode WHERE ino = ?', - this.ino - ).toArray()[0]; - const currentSize = sizeRow?.size ?? 0; - - if (offset > currentSize) { - const zeros = Buffer.alloc(offset - currentSize); - this.writeDataAtOffset(currentSize, zeros); - } - - this.writeDataAtOffset(offset, data); - - const newSize = Math.max(currentSize, offset + data.length); - const now = Math.floor(Date.now() / 1000); - this.storage.sql.exec( - 'UPDATE fs_inode SET size = ?, mtime = ? WHERE ino = ?', - newSize, now, this.ino - ); - }); - } - - private writeDataAtOffset(offset: number, data: Buffer): void { - const startChunk = Math.floor(offset / this.chunkSize); - const endChunk = Math.floor((offset + data.length - 1) / this.chunkSize); - - for (let chunkIdx = startChunk; chunkIdx <= endChunk; chunkIdx++) { - const chunkStart = chunkIdx * this.chunkSize; - const chunkEnd = chunkStart + this.chunkSize; - - const dataStart = Math.max(0, chunkStart - offset); - const dataEnd = Math.min(data.length, chunkEnd - offset); - const writeOffset = Math.max(0, offset - chunkStart); - - const existingRows = this.storage.sql.exec<{ data: ArrayBuffer }>( - 'SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?', - this.ino, chunkIdx - ).toArray(); - - let chunkData: Buffer; - if (existingRows.length > 0) { - chunkData = Buffer.from(existingRows[0].data); - if (writeOffset + (dataEnd - dataStart) > chunkData.length) { - const newChunk = Buffer.alloc(writeOffset + (dataEnd - dataStart)); - chunkData.copy(newChunk); - chunkData = newChunk; - } - } else { - chunkData = Buffer.alloc(writeOffset + (dataEnd - dataStart)); - } - - data.copy(chunkData, writeOffset, dataStart, dataEnd); - - this.storage.sql.exec( - `INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?) - ON CONFLICT(ino, chunk_index) DO UPDATE SET data = excluded.data`, - this.ino, chunkIdx, chunkData - ); - } - } - - async truncate(newSize: number): Promise { - this.storage.transactionSync(() => { - const sizeRow = this.storage.sql.exec<{ size: number }>( - 'SELECT size FROM fs_inode WHERE ino = ?', - this.ino - ).toArray()[0]; - const currentSize = sizeRow?.size ?? 0; - - if (newSize === 0) { - this.storage.sql.exec('DELETE FROM fs_data WHERE ino = ?', this.ino); - } else if (newSize < currentSize) { - const lastChunkIdx = Math.floor((newSize - 1) / this.chunkSize); - - this.storage.sql.exec( - 'DELETE FROM fs_data WHERE ino = ? AND chunk_index > ?', - this.ino, lastChunkIdx - ); - - const offsetInChunk = newSize % this.chunkSize; - if (offsetInChunk > 0) { - const rows = this.storage.sql.exec<{ data: ArrayBuffer }>( - 'SELECT data FROM fs_data WHERE ino = ? AND chunk_index = ?', - this.ino, lastChunkIdx - ).toArray(); - - if (rows.length > 0) { - const existingData = Buffer.from(rows[0].data); - if (existingData.length > offsetInChunk) { - const truncatedChunk = existingData.subarray(0, offsetInChunk); - this.storage.sql.exec( - 'UPDATE fs_data SET data = ? WHERE ino = ? AND chunk_index = ?', - truncatedChunk, this.ino, lastChunkIdx - ); - } - } - } - } - - const now = Math.floor(Date.now() / 1000); - this.storage.sql.exec( - 'UPDATE fs_inode SET size = ?, mtime = ? WHERE ino = ?', - newSize, now, this.ino - ); - }); - } - - async fsync(): Promise { - // Cloudflare Durable Objects automatically persist data - // No explicit sync needed - } - - async fstat(): Promise { - const rows = this.storage.sql.exec<{ - ino: number; - mode: number; - nlink: number; - uid: number; - gid: number; - size: number; - atime: number; - mtime: number; - ctime: number; - }>( - `SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime - FROM fs_inode WHERE ino = ?`, - this.ino - ).toArray(); - - if (rows.length === 0) { - throw new Error('File handle refers to deleted inode'); - } - - return createStats(rows[0]); - } -} - -/** - * A filesystem backed by Cloudflare Durable Objects SQLite storage. - * - * AgentFS implements the FileSystem interface using Cloudflare's - * Durable Objects SQLite storage as the backing store. - * - * @example - * ```typescript - * // In a Durable Object class - * export class MyDurableObject extends DurableObject { - * private fs: AgentFS; - * - * constructor(ctx: DurableObjectState, env: Env) { - * super(ctx, env); - * this.fs = AgentFS.create(ctx.storage); - * } - * - * async fetch(request: Request) { - * await this.fs.writeFile('/hello.txt', 'Hello, World!'); - * const content = await this.fs.readFile('/hello.txt', 'utf8'); - * return new Response(content); - * } - * } - * ``` - */ -export class AgentFS implements FileSystem { - private storage: CloudflareStorage; - private rootIno: number = 1; - private chunkSize: number = DEFAULT_CHUNK_SIZE; - - private constructor(storage: CloudflareStorage) { - this.storage = storage; - } - - /** - * Create a AgentFS from a Durable Object storage context. - * - * @param storage - The ctx.storage from a Durable Object - */ - static create(storage: CloudflareStorage): AgentFS { - const fs = new AgentFS(storage); - fs.initialize(); - return fs; - } - - getChunkSize(): number { - return this.chunkSize; - } - - private initialize(): void { - this.storage.sql.exec(` - CREATE TABLE IF NOT EXISTS fs_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL - ) - `); - - this.storage.sql.exec(` - CREATE TABLE IF NOT EXISTS fs_inode ( - ino INTEGER PRIMARY KEY AUTOINCREMENT, - mode INTEGER NOT NULL, - nlink INTEGER NOT NULL DEFAULT 0, - uid INTEGER NOT NULL DEFAULT 0, - gid INTEGER NOT NULL DEFAULT 0, - size INTEGER NOT NULL DEFAULT 0, - atime INTEGER NOT NULL, - mtime INTEGER NOT NULL, - ctime INTEGER NOT NULL, - rdev INTEGER NOT NULL DEFAULT 0, - atime_nsec INTEGER NOT NULL DEFAULT 0, - mtime_nsec INTEGER NOT NULL DEFAULT 0, - ctime_nsec INTEGER NOT NULL DEFAULT 0 - ) - `); - - this.storage.sql.exec(` - CREATE TABLE IF NOT EXISTS fs_dentry ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - parent_ino INTEGER NOT NULL, - ino INTEGER NOT NULL, - UNIQUE(parent_ino, name) - ) - `); - - this.storage.sql.exec(` - CREATE INDEX IF NOT EXISTS idx_fs_dentry_parent - ON fs_dentry(parent_ino, name) - `); - - this.storage.sql.exec(` - CREATE TABLE IF NOT EXISTS fs_data ( - ino INTEGER NOT NULL, - chunk_index INTEGER NOT NULL, - data BLOB NOT NULL, - PRIMARY KEY (ino, chunk_index) - ) - `); - - this.storage.sql.exec(` - CREATE TABLE IF NOT EXISTS fs_symlink ( - ino INTEGER PRIMARY KEY, - target TEXT NOT NULL - ) - `); - - this.chunkSize = this.ensureRoot(); - } - - private ensureRoot(): number { - const configRows = this.storage.sql.exec<{ value: string }>( - "SELECT value FROM fs_config WHERE key = 'chunk_size'" - ).toArray(); - - let chunkSize: number; - if (configRows.length === 0) { - this.storage.sql.exec( - "INSERT INTO fs_config (key, value) VALUES ('chunk_size', ?)", - DEFAULT_CHUNK_SIZE.toString() - ); - chunkSize = DEFAULT_CHUNK_SIZE; - } else { - chunkSize = parseInt(configRows[0].value, 10) || DEFAULT_CHUNK_SIZE; - } - - // Set schema version (keep in sync with AGENTFS_SCHEMA_VERSION in sdk/rust/src/schema.rs) - this.storage.sql.exec( - "INSERT OR REPLACE INTO fs_config (key, value) VALUES ('schema_version', '0.4')" - ); - - const rootRows = this.storage.sql.exec<{ ino: number }>( - 'SELECT ino FROM fs_inode WHERE ino = ?', - this.rootIno - ).toArray(); - - if (rootRows.length === 0) { - const now = Math.floor(Date.now() / 1000); - this.storage.sql.exec( - `INSERT INTO fs_inode (ino, mode, nlink, uid, gid, size, atime, mtime, ctime) - VALUES (?, ?, 1, 0, 0, 0, ?, ?, ?)`, - this.rootIno, DEFAULT_DIR_MODE, now, now, now - ); - } - - return chunkSize; - } - - private normalizePath(path: string): string { - const normalized = path.replace(/\/+$/, '') || '/'; - return normalized.startsWith('/') ? normalized : '/' + normalized; - } - - private splitPath(path: string): string[] { - const normalized = this.normalizePath(path); - if (normalized === '/') return []; - return normalized.split('/').filter(p => p); - } - - private resolvePath(path: string): number | null { - const normalized = this.normalizePath(path); - - if (normalized === '/') { - return this.rootIno; - } - - const parts = this.splitPath(normalized); - let currentIno = this.rootIno; - - for (const name of parts) { - const rows = this.storage.sql.exec<{ ino: number }>( - 'SELECT ino FROM fs_dentry WHERE parent_ino = ? AND name = ?', - currentIno, name - ).toArray(); - - if (rows.length === 0) { - return null; - } - - currentIno = rows[0].ino; - } - - return currentIno; - } - - private resolvePathOrThrow(path: string, syscall: string): { normalizedPath: string; ino: number } { - const normalizedPath = this.normalizePath(path); - const ino = this.resolvePath(normalizedPath); - if (ino === null) { - throw createFsError({ - code: 'ENOENT', - syscall, - path: normalizedPath, - message: 'no such file or directory', - }); - } - return { normalizedPath, ino }; - } - - private resolveParent(path: string): { parentIno: number; name: string } | null { - const normalized = this.normalizePath(path); - - if (normalized === '/') { - return null; - } - - const parts = this.splitPath(normalized); - const name = parts[parts.length - 1]; - const parentPath = parts.length === 1 ? '/' : '/' + parts.slice(0, -1).join('/'); - - const parentIno = this.resolvePath(parentPath); - - if (parentIno === null) { - return null; - } - - return { parentIno, name }; - } - - private createInode(mode: number, uid: number = 0, gid: number = 0): number { - const now = Math.floor(Date.now() / 1000); - this.storage.sql.exec( - `INSERT INTO fs_inode (mode, uid, gid, size, atime, mtime, ctime) - VALUES (?, ?, ?, 0, ?, ?, ?)`, - mode, uid, gid, now, now, now - ); - - // Get the last inserted rowid - const rows = this.storage.sql.exec<{ ino: number }>( - 'SELECT last_insert_rowid() as ino' - ).toArray(); - - return rows[0].ino; - } - - private createDentry(parentIno: number, name: string, ino: number): void { - this.storage.sql.exec( - 'INSERT INTO fs_dentry (name, parent_ino, ino) VALUES (?, ?, ?)', - name, parentIno, ino - ); - - this.storage.sql.exec( - 'UPDATE fs_inode SET nlink = nlink + 1 WHERE ino = ?', - ino - ); - } - - private ensureParentDirs(path: string): void { - const parts = this.splitPath(path); - parts.pop(); - - let currentIno = this.rootIno; - - for (const name of parts) { - const rows = this.storage.sql.exec<{ ino: number }>( - 'SELECT ino FROM fs_dentry WHERE parent_ino = ? AND name = ?', - currentIno, name - ).toArray(); - - if (rows.length === 0) { - const dirIno = this.createInode(DEFAULT_DIR_MODE); - this.createDentry(currentIno, name, dirIno); - currentIno = dirIno; - } else { - const mode = this.getInodeMode(rows[0].ino); - if (mode !== null && (mode & S_IFMT) !== S_IFDIR) { - throw createFsError({ - code: 'ENOTDIR', - syscall: 'open', - path: this.normalizePath(path), - message: 'not a directory', - }); - } - currentIno = rows[0].ino; - } - } - } - - private getLinkCount(ino: number): number { - const rows = this.storage.sql.exec<{ nlink: number }>( - 'SELECT nlink FROM fs_inode WHERE ino = ?', - ino - ).toArray(); - return rows[0]?.nlink ?? 0; - } - - private getInodeMode(ino: number): number | null { - const rows = this.storage.sql.exec<{ mode: number }>( - 'SELECT mode FROM fs_inode WHERE ino = ?', - ino - ).toArray(); - return rows[0]?.mode ?? null; - } - - // ==================== FileSystem Interface Implementation ==================== - - async writeFile( - path: string, - content: string | Buffer, - options?: BufferEncoding | { encoding?: BufferEncoding } - ): Promise { - const encoding = typeof options === 'string' - ? options - : options?.encoding; - - const buffer = typeof content === 'string' - ? Buffer.from(content, encoding ?? 'utf8') - : content; - - this.storage.transactionSync(() => { - this.ensureParentDirs(path); - - const ino = this.resolvePath(path); - const normalizedPath = this.normalizePath(path); - - if (ino !== null) { - const mode = this.getInodeMode(ino); - if (mode !== null && (mode & S_IFMT) === S_IFDIR) { - throw createFsError({ - code: 'EISDIR', - syscall: 'open', - path: normalizedPath, - message: 'illegal operation on a directory', - }); - } - this.updateFileContent(ino, buffer); - } else { - const parent = this.resolveParent(path); - if (!parent) { - throw createFsError({ - code: 'ENOENT', - syscall: 'open', - path: normalizedPath, - message: 'no such file or directory', - }); - } - - const fileIno = this.createInode(DEFAULT_FILE_MODE); - this.createDentry(parent.parentIno, parent.name, fileIno); - this.updateFileContent(fileIno, buffer); - } - }); - } - - private updateFileContent(ino: number, buffer: Buffer): void { - const now = Math.floor(Date.now() / 1000); - - this.storage.sql.exec('DELETE FROM fs_data WHERE ino = ?', ino); - - if (buffer.length > 0) { - let chunkIndex = 0; - for (let offset = 0; offset < buffer.length; offset += this.chunkSize) { - const chunk = buffer.subarray(offset, Math.min(offset + this.chunkSize, buffer.length)); - this.storage.sql.exec( - 'INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?)', - ino, chunkIndex, chunk - ); - chunkIndex++; - } - } - - this.storage.sql.exec( - 'UPDATE fs_inode SET size = ?, mtime = ? WHERE ino = ?', - buffer.length, now, ino - ); - } - - async readFile(path: string): Promise; - async readFile(path: string, encoding: BufferEncoding): Promise; - async readFile(path: string, options: { encoding: BufferEncoding }): Promise; - async readFile( - path: string, - options?: BufferEncoding | { encoding?: BufferEncoding } - ): Promise { - const encoding = typeof options === 'string' - ? options - : options?.encoding; - - const { normalizedPath, ino } = this.resolvePathOrThrow(path, 'open'); - - const mode = this.getInodeMode(ino); - if (mode !== null && (mode & S_IFMT) === S_IFDIR) { - throw createFsError({ - code: 'EISDIR', - syscall: 'open', - path: normalizedPath, - message: 'illegal operation on a directory', - }); - } - - const rows = this.storage.sql.exec<{ data: ArrayBuffer }>( - 'SELECT data FROM fs_data WHERE ino = ? ORDER BY chunk_index ASC', - ino - ).toArray(); - - let combined: Buffer; - if (rows.length === 0) { - combined = Buffer.alloc(0); - } else { - const buffers = rows.map(row => Buffer.from(row.data)); - combined = Buffer.concat(buffers); - } - - const now = Math.floor(Date.now() / 1000); - this.storage.sql.exec('UPDATE fs_inode SET atime = ? WHERE ino = ?', now, ino); - - if (encoding) { - return combined.toString(encoding); - } - return combined; - } - - async readdir(path: string): Promise { - const { normalizedPath, ino } = this.resolvePathOrThrow(path, 'scandir'); - - const mode = this.getInodeMode(ino); - if (mode !== null && (mode & S_IFMT) !== S_IFDIR) { - throw createFsError({ - code: 'ENOTDIR', - syscall: 'scandir', - path: normalizedPath, - message: 'not a directory', - }); - } - - const rows = this.storage.sql.exec<{ name: string }>( - 'SELECT name FROM fs_dentry WHERE parent_ino = ? ORDER BY name ASC', - ino - ).toArray(); - - return rows.map(row => row.name); - } - - async readdirPlus(path: string): Promise { - const { normalizedPath, ino } = this.resolvePathOrThrow(path, 'scandir'); - - const mode = this.getInodeMode(ino); - if (mode !== null && (mode & S_IFMT) !== S_IFDIR) { - throw createFsError({ - code: 'ENOTDIR', - syscall: 'scandir', - path: normalizedPath, - message: 'not a directory', - }); - } - - const rows = this.storage.sql.exec<{ - name: string; - ino: number; - mode: number; - nlink: number; - uid: number; - gid: number; - size: number; - atime: number; - mtime: number; - ctime: number; - }>( - `SELECT d.name, i.ino, i.mode, i.nlink, i.uid, i.gid, i.size, i.atime, i.mtime, i.ctime - FROM fs_dentry d - JOIN fs_inode i ON d.ino = i.ino - WHERE d.parent_ino = ? - ORDER BY d.name ASC`, - ino - ).toArray(); - - return rows.map(row => ({ - name: row.name, - stats: createStats({ - ino: row.ino, - mode: row.mode, - nlink: row.nlink, - uid: row.uid, - gid: row.gid, - size: row.size, - atime: row.atime, - mtime: row.mtime, - ctime: row.ctime, - }), - })); - } - - async stat(path: string): Promise { - const { normalizedPath, ino } = this.resolvePathOrThrow(path, 'stat'); - - const rows = this.storage.sql.exec<{ - ino: number; - mode: number; - nlink: number; - uid: number; - gid: number; - size: number; - atime: number; - mtime: number; - ctime: number; - }>( - `SELECT ino, mode, nlink, uid, gid, size, atime, mtime, ctime - FROM fs_inode WHERE ino = ?`, - ino - ).toArray(); - - if (rows.length === 0) { - throw createFsError({ - code: 'ENOENT', - syscall: 'stat', - path: normalizedPath, - message: 'no such file or directory', - }); - } - - return createStats(rows[0]); - } - - async lstat(path: string): Promise { - return this.stat(path); - } - - async mkdir(path: string): Promise { - const normalizedPath = this.normalizePath(path); - - const existing = this.resolvePath(normalizedPath); - if (existing !== null) { - throw createFsError({ - code: 'EEXIST', - syscall: 'mkdir', - path: normalizedPath, - message: 'file already exists', - }); - } - - const parent = this.resolveParent(normalizedPath); - if (!parent) { - throw createFsError({ - code: 'ENOENT', - syscall: 'mkdir', - path: normalizedPath, - message: 'no such file or directory', - }); - } - - const parentMode = this.getInodeMode(parent.parentIno); - if (parentMode !== null && (parentMode & S_IFMT) !== S_IFDIR) { - throw createFsError({ - code: 'ENOTDIR', - syscall: 'mkdir', - path: normalizedPath, - message: 'not a directory', - }); - } - - this.storage.transactionSync(() => { - const dirIno = this.createInode(DEFAULT_DIR_MODE); - try { - this.createDentry(parent.parentIno, parent.name, dirIno); - } catch { - throw createFsError({ - code: 'EEXIST', - syscall: 'mkdir', - path: normalizedPath, - message: 'file already exists', - }); - } - }); - } - - async rmdir(path: string): Promise { - const normalizedPath = this.normalizePath(path); - - if (normalizedPath === '/') { - throw createFsError({ - code: 'EPERM', - syscall: 'rmdir', - path: normalizedPath, - message: 'operation not permitted', - }); - } - - const { ino } = this.resolvePathOrThrow(normalizedPath, 'rmdir'); - - const mode = this.getInodeMode(ino); - if (mode === null || (mode & S_IFMT) !== S_IFDIR) { - throw createFsError({ - code: 'ENOTDIR', - syscall: 'rmdir', - path: normalizedPath, - message: 'not a directory', - }); - } - - const children = this.storage.sql.exec<{ one: number }>( - 'SELECT 1 as one FROM fs_dentry WHERE parent_ino = ? LIMIT 1', - ino - ).toArray(); - - if (children.length > 0) { - throw createFsError({ - code: 'ENOTEMPTY', - syscall: 'rmdir', - path: normalizedPath, - message: 'directory not empty', - }); - } - - const parent = this.resolveParent(normalizedPath); - if (!parent) { - throw createFsError({ - code: 'EPERM', - syscall: 'rmdir', - path: normalizedPath, - message: 'operation not permitted', - }); - } - - this.removeDentryAndMaybeInode(parent.parentIno, parent.name, ino); - } - - async unlink(path: string): Promise { - const normalizedPath = this.normalizePath(path); - - if (normalizedPath === '/') { - throw createFsError({ - code: 'EPERM', - syscall: 'unlink', - path: normalizedPath, - message: 'operation not permitted', - }); - } - - const { ino } = this.resolvePathOrThrow(normalizedPath, 'unlink'); - - const mode = this.getInodeMode(ino); - if (mode !== null && (mode & S_IFMT) === S_IFDIR) { - throw createFsError({ - code: 'EISDIR', - syscall: 'unlink', - path: normalizedPath, - message: 'illegal operation on a directory', - }); - } - - const parent = this.resolveParent(normalizedPath)!; - - this.storage.transactionSync(() => { - this.storage.sql.exec( - 'DELETE FROM fs_dentry WHERE parent_ino = ? AND name = ?', - parent.parentIno, parent.name - ); - - this.storage.sql.exec( - 'UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?', - ino - ); - - const linkCount = this.getLinkCount(ino); - if (linkCount === 0) { - this.storage.sql.exec('DELETE FROM fs_inode WHERE ino = ?', ino); - this.storage.sql.exec('DELETE FROM fs_data WHERE ino = ?', ino); - } - }); - } - - async rm( - path: string, - options?: { force?: boolean; recursive?: boolean } - ): Promise { - const normalizedPath = this.normalizePath(path); - const force = options?.force ?? false; - const recursive = options?.recursive ?? false; - - if (normalizedPath === '/') { - throw createFsError({ - code: 'EPERM', - syscall: 'rm', - path: normalizedPath, - message: 'operation not permitted', - }); - } - - const ino = this.resolvePath(normalizedPath); - if (ino === null) { - if (!force) { - throw createFsError({ - code: 'ENOENT', - syscall: 'rm', - path: normalizedPath, - message: 'no such file or directory', - }); - } - return; - } - - const mode = this.getInodeMode(ino); - if (mode === null) { - if (!force) { - throw createFsError({ - code: 'ENOENT', - syscall: 'rm', - path: normalizedPath, - message: 'no such file or directory', - }); - } - return; - } - - const parent = this.resolveParent(normalizedPath); - if (!parent) { - throw createFsError({ - code: 'EPERM', - syscall: 'rm', - path: normalizedPath, - message: 'operation not permitted', - }); - } - - if ((mode & S_IFMT) === S_IFDIR) { - if (!recursive) { - throw createFsError({ - code: 'EISDIR', - syscall: 'rm', - path: normalizedPath, - message: 'illegal operation on a directory', - }); - } - - this.storage.transactionSync(() => { - this.rmDirContentsRecursive(ino); - this.removeDentryAndMaybeInode(parent.parentIno, parent.name, ino); - }); - return; - } - - this.removeDentryAndMaybeInode(parent.parentIno, parent.name, ino); - } - - private rmDirContentsRecursive(dirIno: number): void { - const children = this.storage.sql.exec<{ name: string; ino: number }>( - 'SELECT name, ino FROM fs_dentry WHERE parent_ino = ? ORDER BY name ASC', - dirIno - ).toArray(); - - for (const child of children) { - const mode = this.getInodeMode(child.ino); - if (mode === null) { - continue; - } - - if ((mode & S_IFMT) === S_IFDIR) { - this.rmDirContentsRecursive(child.ino); - this.removeDentryAndMaybeInode(dirIno, child.name, child.ino); - } else { - this.removeDentryAndMaybeInode(dirIno, child.name, child.ino); - } - } - } - - private removeDentryAndMaybeInode(parentIno: number, name: string, ino: number): void { - this.storage.sql.exec( - 'DELETE FROM fs_dentry WHERE parent_ino = ? AND name = ?', - parentIno, name - ); - - this.storage.sql.exec( - 'UPDATE fs_inode SET nlink = nlink - 1 WHERE ino = ?', - ino - ); - - const linkCount = this.getLinkCount(ino); - if (linkCount === 0) { - this.storage.sql.exec('DELETE FROM fs_inode WHERE ino = ?', ino); - this.storage.sql.exec('DELETE FROM fs_data WHERE ino = ?', ino); - this.storage.sql.exec('DELETE FROM fs_symlink WHERE ino = ?', ino); - } - } - - async rename(oldPath: string, newPath: string): Promise { - const oldNormalized = this.normalizePath(oldPath); - const newNormalized = this.normalizePath(newPath); - - if (oldNormalized === newNormalized) return; - - if (oldNormalized === '/' || newNormalized === '/') { - throw createFsError({ - code: 'EPERM', - syscall: 'rename', - path: oldNormalized === '/' ? oldNormalized : newNormalized, - message: 'operation not permitted', - }); - } - - const oldParent = this.resolveParent(oldNormalized); - if (!oldParent) { - throw createFsError({ - code: 'EPERM', - syscall: 'rename', - path: oldNormalized, - message: 'operation not permitted', - }); - } - - const newParent = this.resolveParent(newNormalized); - if (!newParent) { - throw createFsError({ - code: 'ENOENT', - syscall: 'rename', - path: newNormalized, - message: 'no such file or directory', - }); - } - - this.storage.transactionSync(() => { - const { ino: oldIno } = this.resolvePathOrThrow(oldNormalized, 'rename'); - const oldMode = this.getInodeMode(oldIno); - if (oldMode === null) { - throw createFsError({ - code: 'ENOENT', - syscall: 'rename', - path: oldNormalized, - message: 'no such file or directory', - }); - } - const oldIsDir = (oldMode & S_IFMT) === S_IFDIR; - - if (oldIsDir && newNormalized.startsWith(oldNormalized + '/')) { - throw createFsError({ - code: 'EINVAL', - syscall: 'rename', - path: newNormalized, - message: 'invalid argument', - }); - } - - const newIno = this.resolvePath(newNormalized); - if (newIno !== null) { - const newMode = this.getInodeMode(newIno); - if (newMode !== null) { - const newIsDir = (newMode & S_IFMT) === S_IFDIR; - - if (newIsDir && !oldIsDir) { - throw createFsError({ - code: 'EISDIR', - syscall: 'rename', - path: newNormalized, - message: 'illegal operation on a directory', - }); - } - if (!newIsDir && oldIsDir) { - throw createFsError({ - code: 'ENOTDIR', - syscall: 'rename', - path: newNormalized, - message: 'not a directory', - }); - } - - if (newIsDir) { - const children = this.storage.sql.exec<{ one: number }>( - 'SELECT 1 as one FROM fs_dentry WHERE parent_ino = ? LIMIT 1', - newIno - ).toArray(); - if (children.length > 0) { - throw createFsError({ - code: 'ENOTEMPTY', - syscall: 'rename', - path: newNormalized, - message: 'directory not empty', - }); - } - } - - this.removeDentryAndMaybeInode(newParent.parentIno, newParent.name, newIno); - } - } - - this.storage.sql.exec( - 'UPDATE fs_dentry SET parent_ino = ?, name = ? WHERE parent_ino = ? AND name = ?', - newParent.parentIno, newParent.name, oldParent.parentIno, oldParent.name - ); - - const now = Math.floor(Date.now() / 1000); - this.storage.sql.exec('UPDATE fs_inode SET ctime = ? WHERE ino = ?', now, oldIno); - this.storage.sql.exec('UPDATE fs_inode SET mtime = ?, ctime = ? WHERE ino = ?', now, now, oldParent.parentIno); - if (newParent.parentIno !== oldParent.parentIno) { - this.storage.sql.exec('UPDATE fs_inode SET mtime = ?, ctime = ? WHERE ino = ?', now, now, newParent.parentIno); - } - }); - } - - async copyFile(src: string, dest: string): Promise { - const srcNormalized = this.normalizePath(src); - const destNormalized = this.normalizePath(dest); - - if (srcNormalized === destNormalized) { - throw createFsError({ - code: 'EINVAL', - syscall: 'copyfile', - path: destNormalized, - message: 'invalid argument', - }); - } - - const { ino: srcIno } = this.resolvePathOrThrow(srcNormalized, 'copyfile'); - - const srcMode = this.getInodeMode(srcIno); - if (srcMode !== null && (srcMode & S_IFMT) === S_IFDIR) { - throw createFsError({ - code: 'EISDIR', - syscall: 'copyfile', - path: srcNormalized, - message: 'illegal operation on a directory', - }); - } - - const srcRows = this.storage.sql.exec<{ mode: number; uid: number; gid: number; size: number }>( - 'SELECT mode, uid, gid, size FROM fs_inode WHERE ino = ?', - srcIno - ).toArray(); - - if (srcRows.length === 0) { - throw createFsError({ - code: 'ENOENT', - syscall: 'copyfile', - path: srcNormalized, - message: 'no such file or directory', - }); - } - - const srcRow = srcRows[0]; - - const destParent = this.resolveParent(destNormalized); - if (!destParent) { - throw createFsError({ - code: 'ENOENT', - syscall: 'copyfile', - path: destNormalized, - message: 'no such file or directory', - }); - } - - this.storage.transactionSync(() => { - const now = Math.floor(Date.now() / 1000); - - const destIno = this.resolvePath(destNormalized); - if (destIno !== null) { - const destMode = this.getInodeMode(destIno); - if (destMode !== null && (destMode & S_IFMT) === S_IFDIR) { - throw createFsError({ - code: 'EISDIR', - syscall: 'copyfile', - path: destNormalized, - message: 'illegal operation on a directory', - }); - } - - this.storage.sql.exec('DELETE FROM fs_data WHERE ino = ?', destIno); - - // Copy data chunks - const srcData = this.storage.sql.exec<{ chunk_index: number; data: ArrayBuffer }>( - 'SELECT chunk_index, data FROM fs_data WHERE ino = ? ORDER BY chunk_index ASC', - srcIno - ).toArray(); - - for (const chunk of srcData) { - this.storage.sql.exec( - 'INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?)', - destIno, chunk.chunk_index, Buffer.from(chunk.data) - ); - } - - this.storage.sql.exec( - 'UPDATE fs_inode SET mode = ?, uid = ?, gid = ?, size = ?, mtime = ?, ctime = ? WHERE ino = ?', - srcRow.mode, srcRow.uid, srcRow.gid, srcRow.size, now, now, destIno - ); - } else { - const destInoCreated = this.createInode(srcRow.mode, srcRow.uid, srcRow.gid); - this.createDentry(destParent.parentIno, destParent.name, destInoCreated); - - // Copy data chunks - const srcData = this.storage.sql.exec<{ chunk_index: number; data: ArrayBuffer }>( - 'SELECT chunk_index, data FROM fs_data WHERE ino = ? ORDER BY chunk_index ASC', - srcIno - ).toArray(); - - for (const chunk of srcData) { - this.storage.sql.exec( - 'INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, ?, ?)', - destInoCreated, chunk.chunk_index, Buffer.from(chunk.data) - ); - } - - this.storage.sql.exec( - 'UPDATE fs_inode SET size = ?, mtime = ?, ctime = ? WHERE ino = ?', - srcRow.size, now, now, destInoCreated - ); - } - }); - } - - async symlink(target: string, linkpath: string): Promise { - const normalizedLinkpath = this.normalizePath(linkpath); - - const existing = this.resolvePath(normalizedLinkpath); - if (existing !== null) { - throw createFsError({ - code: 'EEXIST', - syscall: 'open', - path: normalizedLinkpath, - message: 'file already exists', - }); - } - - const parent = this.resolveParent(normalizedLinkpath); - if (!parent) { - throw createFsError({ - code: 'ENOENT', - syscall: 'open', - path: normalizedLinkpath, - message: 'no such file or directory', - }); - } - - this.storage.transactionSync(() => { - const mode = S_IFLNK | 0o777; - const symlinkIno = this.createInode(mode); - this.createDentry(parent.parentIno, parent.name, symlinkIno); - - this.storage.sql.exec( - 'INSERT INTO fs_symlink (ino, target) VALUES (?, ?)', - symlinkIno, target - ); - - this.storage.sql.exec( - 'UPDATE fs_inode SET size = ? WHERE ino = ?', - target.length, symlinkIno - ); - }); - } - - async readlink(path: string): Promise { - const { normalizedPath, ino } = this.resolvePathOrThrow(path, 'open'); - - const mode = this.getInodeMode(ino); - if (mode === null || (mode & S_IFMT) !== S_IFLNK) { - throw createFsError({ - code: 'EINVAL', - syscall: 'open', - path: normalizedPath, - message: 'invalid argument', - }); - } - - const rows = this.storage.sql.exec<{ target: string }>( - 'SELECT target FROM fs_symlink WHERE ino = ?', - ino - ).toArray(); - - if (rows.length === 0) { - throw createFsError({ - code: 'ENOENT', - syscall: 'open', - path: normalizedPath, - message: 'no such file or directory', - }); - } - - return rows[0].target; - } - - async access(path: string): Promise { - const normalizedPath = this.normalizePath(path); - const ino = this.resolvePath(normalizedPath); - if (ino === null) { - throw createFsError({ - code: 'ENOENT', - syscall: 'access', - path: normalizedPath, - message: 'no such file or directory', - }); - } - } - - async statfs(): Promise { - const inodeRows = this.storage.sql.exec<{ count: number }>( - 'SELECT COUNT(*) as count FROM fs_inode' - ).toArray(); - - const bytesRows = this.storage.sql.exec<{ total: number }>( - 'SELECT COALESCE(SUM(LENGTH(data)), 0) as total FROM fs_data' - ).toArray(); - - return { - inodes: inodeRows[0].count, - bytesUsed: bytesRows[0].total, - }; - } - - async open(path: string): Promise { - const { normalizedPath, ino } = this.resolvePathOrThrow(path, 'open'); - - const mode = this.getInodeMode(ino); - if (mode !== null && (mode & S_IFMT) === S_IFDIR) { - throw createFsError({ - code: 'EISDIR', - syscall: 'open', - path: normalizedPath, - message: 'illegal operation on a directory', - }); - } - - return new AgentFSFile(this.storage, ino, this.chunkSize); - } - - // Legacy alias - async deleteFile(path: string): Promise { - return await this.unlink(path); - } -} diff --git a/sdk/typescript/src/integrations/cloudflare/index.ts b/sdk/typescript/src/integrations/cloudflare/index.ts deleted file mode 100644 index 5bcdcca0..00000000 --- a/sdk/typescript/src/integrations/cloudflare/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Cloudflare Durable Objects integration for AgentFS. - * - * Provides AgentFS - a FileSystem implementation that uses - * Cloudflare Durable Objects SQLite storage as its backend. - * - * @example - * ```typescript - * import { AgentFS } from "agentfs-sdk/cloudflare"; - * - * export class MyDurableObject extends DurableObject { - * private fs: AgentFS; - * - * constructor(ctx: DurableObjectState, env: Env) { - * super(ctx, env); - * this.fs = AgentFS.create(ctx.storage); - * } - * - * async fetch(request: Request) { - * await this.fs.writeFile('/hello.txt', 'Hello, World!'); - * const content = await this.fs.readFile('/hello.txt', 'utf8'); - * return new Response(content); - * } - * } - * ``` - * - * @see https://developers.cloudflare.com/durable-objects/ - */ - -export { AgentFS, type CloudflareStorage } from "./agentfs.js"; - -export type { - FileSystem, - Stats, - DirEntry, - FilesystemStats, - FileHandle, -} from "../../filesystem/interface.js"; diff --git a/sdk/typescript/src/integrations/just-bash/AgentFs.test.ts b/sdk/typescript/src/integrations/just-bash/AgentFs.test.ts deleted file mode 100644 index 24cd6199..00000000 --- a/sdk/typescript/src/integrations/just-bash/AgentFs.test.ts +++ /dev/null @@ -1,687 +0,0 @@ -import { AgentFS } from "../../index_node.js"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { AgentFsWrapper } from "./AgentFs.js"; - -describe("AgentFsWrapper", () => { - let agentHandle: Awaited>; - let fs: AgentFsWrapper; - - beforeEach(async () => { - // Use in-memory database for tests - agentHandle = await AgentFS.open({ path: ":memory:" }); - fs = new AgentFsWrapper({ fs: agentHandle }); - }); - - afterEach(async () => { - await agentHandle.close(); - }); - - describe("constructor", () => { - it("should create with default mount point", () => { - expect(fs.getMountPoint()).toBe("/"); - }); - - it("should accept custom mount point", () => { - const customFs = new AgentFsWrapper({ - fs: agentHandle, - mountPoint: "/mnt/data", - }); - expect(customFs.getMountPoint()).toBe("/mnt/data"); - }); - - it("should normalize mount point with trailing slash", () => { - const customFs = new AgentFsWrapper({ - fs: agentHandle, - mountPoint: "/mnt/data/", - }); - expect(customFs.getMountPoint()).toBe("/mnt/data"); - }); - - it("should throw for non-absolute mount point", () => { - expect( - () => new AgentFsWrapper({ fs: agentHandle, mountPoint: "relative" }), - ).toThrow("Mount point must be an absolute path"); - }); - }); - - describe("readFile", () => { - it("should read file as utf8 by default", async () => { - await fs.writeFile("/test.txt", "Hello, World!"); - const content = await fs.readFile("/test.txt"); - expect(content).toBe("Hello, World!"); - }); - - it("should read file with explicit utf8 encoding", async () => { - await fs.writeFile("/test.txt", "Hello"); - const content = await fs.readFile("/test.txt", "utf8"); - expect(content).toBe("Hello"); - }); - - it("should read file with utf-8 encoding", async () => { - await fs.writeFile("/test.txt", "Hello"); - const content = await fs.readFile("/test.txt", "utf-8"); - expect(content).toBe("Hello"); - }); - - it("should read file as base64", async () => { - await fs.writeFile("/test.txt", "Hello"); - const content = await fs.readFile("/test.txt", "base64"); - expect(content).toBe("SGVsbG8="); - }); - - it("should read file as hex", async () => { - await fs.writeFile("/test.txt", "Hi"); - const content = await fs.readFile("/test.txt", "hex"); - expect(content).toBe("4869"); - }); - - it("should read file as binary/latin1", async () => { - const data = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" - await fs.writeFile("/test.bin", data); - const content = await fs.readFile("/test.bin", "binary"); - expect(content).toBe("Hello"); - }); - - it("should read file with options object", async () => { - await fs.writeFile("/test.txt", "Test"); - const content = await fs.readFile("/test.txt", { encoding: "base64" }); - expect(content).toBe("VGVzdA=="); - }); - - it("should throw ENOENT for non-existent file", async () => { - await expect(fs.readFile("/nonexistent.txt")).rejects.toThrow("ENOENT"); - }); - }); - - describe("readFileBuffer", () => { - it("should read file as Uint8Array", async () => { - await fs.writeFile("/test.txt", "Hello"); - const buffer = await fs.readFileBuffer("/test.txt"); - expect(buffer).toBeInstanceOf(Uint8Array); - expect(buffer).toEqual(new Uint8Array([72, 101, 108, 108, 111])); - }); - - it("should read binary data correctly", async () => { - const data = new Uint8Array([0, 1, 2, 255, 254, 253]); - await fs.writeFile("/binary.bin", data); - const buffer = await fs.readFileBuffer("/binary.bin"); - expect(buffer).toEqual(data); - }); - - it("should throw ENOENT for non-existent file", async () => { - await expect(fs.readFileBuffer("/nonexistent.bin")).rejects.toThrow( - "ENOENT", - ); - }); - }); - - describe("writeFile", () => { - it("should write string content", async () => { - await fs.writeFile("/test.txt", "Hello, World!"); - const content = await fs.readFile("/test.txt"); - expect(content).toBe("Hello, World!"); - }); - - it("should write binary data", async () => { - const data = new Uint8Array([1, 2, 3, 4, 5]); - await fs.writeFile("/binary.bin", data); - const buffer = await fs.readFileBuffer("/binary.bin"); - expect(buffer).toEqual(data); - }); - - it("should write with base64 encoding", async () => { - await fs.writeFile("/test.txt", "SGVsbG8=", "base64"); - const content = await fs.readFile("/test.txt"); - expect(content).toBe("Hello"); - }); - - it("should write with hex encoding", async () => { - await fs.writeFile("/test.txt", "48656c6c6f", "hex"); - const content = await fs.readFile("/test.txt"); - expect(content).toBe("Hello"); - }); - - it("should write with binary/latin1 encoding", async () => { - await fs.writeFile("/test.txt", "Hello", "latin1"); - const buffer = await fs.readFileBuffer("/test.txt"); - expect(buffer).toEqual(new Uint8Array([72, 101, 108, 108, 111])); - }); - - it("should write with options object", async () => { - await fs.writeFile("/test.txt", "SGk=", { encoding: "base64" }); - const content = await fs.readFile("/test.txt"); - expect(content).toBe("Hi"); - }); - - it("should overwrite existing file", async () => { - await fs.writeFile("/test.txt", "First"); - await fs.writeFile("/test.txt", "Second"); - const content = await fs.readFile("/test.txt"); - expect(content).toBe("Second"); - }); - - it("should create parent directories implicitly", async () => { - await fs.writeFile("/a/b/c/file.txt", "nested"); - const content = await fs.readFile("/a/b/c/file.txt"); - expect(content).toBe("nested"); - }); - }); - - describe("appendFile", () => { - it("should append to existing file", async () => { - await fs.writeFile("/test.txt", "Hello"); - await fs.appendFile("/test.txt", ", World!"); - const content = await fs.readFile("/test.txt"); - expect(content).toBe("Hello, World!"); - }); - - it("should create file if it does not exist", async () => { - await fs.appendFile("/new.txt", "New content"); - const content = await fs.readFile("/new.txt"); - expect(content).toBe("New content"); - }); - - it("should append binary data", async () => { - await fs.writeFile("/test.bin", new Uint8Array([1, 2, 3])); - await fs.appendFile("/test.bin", new Uint8Array([4, 5])); - const buffer = await fs.readFileBuffer("/test.bin"); - expect(buffer).toEqual(new Uint8Array([1, 2, 3, 4, 5])); - }); - - it("should append with encoding", async () => { - await fs.writeFile("/test.txt", "Hello"); - await fs.appendFile("/test.txt", "V29ybGQ=", "base64"); // "World" - const content = await fs.readFile("/test.txt"); - expect(content).toBe("HelloWorld"); - }); - - it("should append multiple times", async () => { - await fs.appendFile("/test.txt", "A"); - await fs.appendFile("/test.txt", "B"); - await fs.appendFile("/test.txt", "C"); - const content = await fs.readFile("/test.txt"); - expect(content).toBe("ABC"); - }); - }); - - describe("exists", () => { - it("should return true for existing file", async () => { - await fs.writeFile("/test.txt", "content"); - expect(await fs.exists("/test.txt")).toBe(true); - }); - - it("should return false for non-existent file", async () => { - expect(await fs.exists("/nonexistent.txt")).toBe(false); - }); - - it("should return true for directory", async () => { - await fs.mkdir("/mydir"); - expect(await fs.exists("/mydir")).toBe(true); - }); - - it("should return true for nested path", async () => { - await fs.writeFile("/a/b/c.txt", "content"); - expect(await fs.exists("/a/b/c.txt")).toBe(true); - expect(await fs.exists("/a/b")).toBe(true); - expect(await fs.exists("/a")).toBe(true); - }); - - it("should return false for partial path", async () => { - await fs.writeFile("/abc.txt", "content"); - expect(await fs.exists("/ab")).toBe(false); - }); - }); - - describe("stat", () => { - it("should return file stats", async () => { - await fs.writeFile("/test.txt", "Hello"); - const stat = await fs.stat("/test.txt"); - expect(stat.isFile).toBe(true); - expect(stat.isDirectory).toBe(false); - expect(stat.isSymbolicLink).toBe(false); - expect(stat.size).toBe(5); - }); - - it("should return directory stats", async () => { - await fs.mkdir("/mydir"); - const stat = await fs.stat("/mydir"); - expect(stat.isFile).toBe(false); - expect(stat.isDirectory).toBe(true); - expect(stat.isSymbolicLink).toBe(false); - }); - - it("should return mode", async () => { - await fs.writeFile("/test.txt", "content"); - const stat = await fs.stat("/test.txt"); - expect(typeof stat.mode).toBe("number"); - }); - - it("should return mtime as Date", async () => { - await fs.writeFile("/test.txt", "content"); - const stat = await fs.stat("/test.txt"); - expect(stat.mtime).toBeInstanceOf(Date); - }); - - it("should throw ENOENT for non-existent path", async () => { - await expect(fs.stat("/nonexistent")).rejects.toThrow("ENOENT"); - }); - - it("should return correct size for binary data", async () => { - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - await fs.writeFile("/binary.bin", data); - const stat = await fs.stat("/binary.bin"); - expect(stat.size).toBe(10); - }); - }); - - describe("lstat", () => { - it("should return file stats", async () => { - await fs.writeFile("/file.txt", "content"); - const stat = await fs.lstat("/file.txt"); - expect(stat.isFile).toBe(true); - expect(stat.isSymbolicLink).toBe(false); - }); - - it("should return directory stats", async () => { - await fs.mkdir("/dir"); - const stat = await fs.lstat("/dir"); - expect(stat.isDirectory).toBe(true); - }); - - it("should throw ENOENT for non-existent path", async () => { - await expect(fs.lstat("/nonexistent")).rejects.toThrow("ENOENT"); - }); - }); - - describe("mkdir", () => { - it("should create a directory", async () => { - await fs.mkdir("/newdir"); - expect(await fs.exists("/newdir")).toBe(true); - const stat = await fs.stat("/newdir"); - expect(stat.isDirectory).toBe(true); - }); - - it("should throw EEXIST if directory exists", async () => { - await fs.mkdir("/mydir"); - await expect(fs.mkdir("/mydir")).rejects.toThrow("EEXIST"); - }); - - it("should throw EEXIST if file exists at path", async () => { - await fs.writeFile("/myfile", "content"); - await expect(fs.mkdir("/myfile")).rejects.toThrow("EEXIST"); - }); - - it("should not throw with recursive if directory exists", async () => { - await fs.mkdir("/mydir"); - await fs.mkdir("/mydir", { recursive: true }); - }); - - it("should create parent directories with recursive", async () => { - await fs.mkdir("/a/b/c", { recursive: true }); - expect(await fs.exists("/a")).toBe(true); - expect(await fs.exists("/a/b")).toBe(true); - expect(await fs.exists("/a/b/c")).toBe(true); - }); - - it("should throw ENOENT without recursive if parent missing", async () => { - await expect(fs.mkdir("/missing/subdir")).rejects.toThrow("ENOENT"); - }); - - it("should create deeply nested directories", async () => { - await fs.mkdir("/a/b/c/d/e/f", { recursive: true }); - expect(await fs.exists("/a/b/c/d/e/f")).toBe(true); - }); - }); - - describe("readdir", () => { - it("should list directory contents", async () => { - await fs.writeFile("/dir/file1.txt", "a"); - await fs.writeFile("/dir/file2.txt", "b"); - await fs.mkdir("/dir/subdir"); - const entries = await fs.readdir("/dir"); - expect(entries.sort()).toEqual(["file1.txt", "file2.txt", "subdir"]); - }); - - it("should return empty array for empty directory", async () => { - await fs.mkdir("/emptydir"); - const entries = await fs.readdir("/emptydir"); - expect(entries).toEqual([]); - }); - - it("should return sorted entries", async () => { - await fs.writeFile("/dir/z.txt", ""); - await fs.writeFile("/dir/a.txt", ""); - await fs.writeFile("/dir/m.txt", ""); - const entries = await fs.readdir("/dir"); - expect(entries).toEqual(["a.txt", "m.txt", "z.txt"]); - }); - - it("should throw ENOENT for non-existent directory", async () => { - await expect(fs.readdir("/nonexistent")).rejects.toThrow("ENOENT"); - }); - }); - - describe("rm", () => { - it("should remove a file", async () => { - await fs.writeFile("/test.txt", "content"); - await fs.rm("/test.txt"); - expect(await fs.exists("/test.txt")).toBe(false); - }); - - it("should throw ENOENT for non-existent file", async () => { - await expect(fs.rm("/nonexistent.txt")).rejects.toThrow("ENOENT"); - }); - - it("should not throw with force for non-existent file", async () => { - await fs.rm("/nonexistent.txt", { force: true }); - }); - - it("should remove empty directory", async () => { - await fs.mkdir("/emptydir"); - await fs.rm("/emptydir"); - expect(await fs.exists("/emptydir")).toBe(false); - }); - - it("should throw ENOTEMPTY for non-empty directory", async () => { - await fs.writeFile("/dir/file.txt", "content"); - await expect(fs.rm("/dir")).rejects.toThrow("ENOTEMPTY"); - }); - - it("should remove non-empty directory with recursive", async () => { - await fs.writeFile("/dir/file.txt", "content"); - await fs.mkdir("/dir/subdir"); - await fs.writeFile("/dir/subdir/nested.txt", "nested"); - await fs.rm("/dir", { recursive: true }); - expect(await fs.exists("/dir/file.txt")).toBe(false); - expect(await fs.exists("/dir/subdir/nested.txt")).toBe(false); - }); - - it("should handle recursive + force together", async () => { - await fs.rm("/nonexistent", { recursive: true, force: true }); - }); - }); - - describe("cp", () => { - it("should copy a file", async () => { - await fs.writeFile("/src.txt", "content"); - await fs.cp("/src.txt", "/dest.txt"); - expect(await fs.readFile("/dest.txt")).toBe("content"); - // Source should still exist - expect(await fs.exists("/src.txt")).toBe(true); - }); - - it("should copy binary file", async () => { - const data = new Uint8Array([0, 1, 2, 255, 254]); - await fs.writeFile("/src.bin", data); - await fs.cp("/src.bin", "/dest.bin"); - const buffer = await fs.readFileBuffer("/dest.bin"); - expect(buffer).toEqual(data); - }); - - it("should overwrite destination", async () => { - await fs.writeFile("/src.txt", "new content"); - await fs.writeFile("/dest.txt", "old content"); - await fs.cp("/src.txt", "/dest.txt"); - expect(await fs.readFile("/dest.txt")).toBe("new content"); - }); - - it("should throw ENOENT for non-existent source", async () => { - await expect(fs.cp("/nonexistent", "/dest")).rejects.toThrow("ENOENT"); - }); - - it("should throw EISDIR for directory without recursive", async () => { - await fs.mkdir("/dir"); - await expect(fs.cp("/dir", "/dest")).rejects.toThrow("EISDIR"); - }); - - it("should copy directory with recursive", async () => { - await fs.writeFile("/dir/file.txt", "content"); - await fs.cp("/dir", "/dest", { recursive: true }); - expect(await fs.readFile("/dest/file.txt")).toBe("content"); - }); - - it("should copy nested directory structure", async () => { - await fs.writeFile("/src/a/b/c.txt", "nested"); - await fs.writeFile("/src/a/d.txt", "sibling"); - await fs.cp("/src", "/dest", { recursive: true }); - expect(await fs.readFile("/dest/a/b/c.txt")).toBe("nested"); - expect(await fs.readFile("/dest/a/d.txt")).toBe("sibling"); - }); - }); - - describe("mv", () => { - it("should move a file", async () => { - await fs.writeFile("/src.txt", "content"); - await fs.mv("/src.txt", "/dest.txt"); - expect(await fs.exists("/src.txt")).toBe(false); - expect(await fs.readFile("/dest.txt")).toBe("content"); - }); - - it("should move a directory", async () => { - await fs.writeFile("/dir/file.txt", "content"); - await fs.mv("/dir", "/newdir"); - expect(await fs.exists("/dir/file.txt")).toBe(false); - expect(await fs.readFile("/newdir/file.txt")).toBe("content"); - }); - - it("should rename a file in same directory", async () => { - await fs.writeFile("/old.txt", "content"); - await fs.mv("/old.txt", "/new.txt"); - expect(await fs.exists("/old.txt")).toBe(false); - expect(await fs.readFile("/new.txt")).toBe("content"); - }); - - it("should move file to different directory", async () => { - await fs.writeFile("/a/file.txt", "content"); - await fs.mkdir("/b"); - await fs.mv("/a/file.txt", "/b/file.txt"); - expect(await fs.exists("/a/file.txt")).toBe(false); - expect(await fs.readFile("/b/file.txt")).toBe("content"); - }); - - it("should throw ENOENT for non-existent source", async () => { - await expect(fs.mv("/nonexistent", "/dest")).rejects.toThrow("ENOENT"); - }); - }); - - describe("symlink", () => { - it("should create a symlink-like file", async () => { - await fs.writeFile("/target.txt", "content"); - await fs.symlink("/target.txt", "/link.txt"); - // Our symlink implementation stores a JSON marker - const linkContent = await fs.readFile("/link.txt"); - expect(linkContent).toContain("__symlink"); - expect(linkContent).toContain("/target.txt"); - }); - - it("should support relative symlink targets", async () => { - await fs.symlink("../other.txt", "/dir/link.txt"); - const target = await fs.readlink("/dir/link.txt"); - expect(target).toBe("../other.txt"); - }); - - it("should throw EEXIST if path exists", async () => { - await fs.writeFile("/file.txt", "content"); - await expect(fs.symlink("/target", "/file.txt")).rejects.toThrow( - "EEXIST", - ); - }); - - it("should allow creating symlink to non-existent target", async () => { - await fs.symlink("/nonexistent", "/link.txt"); - const target = await fs.readlink("/link.txt"); - expect(target).toBe("/nonexistent"); - }); - }); - - describe("readlink", () => { - it("should read symlink target", async () => { - await fs.symlink("/target.txt", "/link.txt"); - const target = await fs.readlink("/link.txt"); - expect(target).toBe("/target.txt"); - }); - - it("should read relative symlink target", async () => { - await fs.symlink("../relative/path", "/dir/link.txt"); - const target = await fs.readlink("/dir/link.txt"); - expect(target).toBe("../relative/path"); - }); - - it("should throw ENOENT for non-existent path", async () => { - await expect(fs.readlink("/nonexistent")).rejects.toThrow("ENOENT"); - }); - - it("should throw EINVAL for non-symlink file", async () => { - await fs.writeFile("/file.txt", "content"); - await expect(fs.readlink("/file.txt")).rejects.toThrow("EINVAL"); - }); - - it("should throw EINVAL for directory", async () => { - await fs.mkdir("/dir"); - await expect(fs.readlink("/dir")).rejects.toThrow("EINVAL"); - }); - }); - - describe("link", () => { - it("should create a hard link (copy)", async () => { - await fs.writeFile("/original.txt", "content"); - await fs.link("/original.txt", "/hardlink.txt"); - expect(await fs.readFile("/hardlink.txt")).toBe("content"); - }); - - it("should create independent copy", async () => { - await fs.writeFile("/original.txt", "original"); - await fs.link("/original.txt", "/copy.txt"); - await fs.writeFile("/original.txt", "modified"); - // Since AgentFS uses copyFile, the "hard link" is actually a copy - expect(await fs.readFile("/copy.txt")).toBe("original"); - }); - - it("should throw ENOENT for non-existent source", async () => { - await expect(fs.link("/nonexistent", "/link")).rejects.toThrow("ENOENT"); - }); - - it("should throw EEXIST if destination exists", async () => { - await fs.writeFile("/src.txt", "a"); - await fs.writeFile("/dest.txt", "b"); - await expect(fs.link("/src.txt", "/dest.txt")).rejects.toThrow("EEXIST"); - }); - - it("should throw EPERM when source is directory", async () => { - await fs.mkdir("/dir"); - await expect(fs.link("/dir", "/link")).rejects.toThrow("EPERM"); - }); - }); - - describe("chmod", () => { - it("should not throw for existing file", async () => { - await fs.writeFile("/file.txt", "content"); - // chmod is a no-op but shouldn't throw - await fs.chmod("/file.txt", 0o755); - }); - - it("should not throw for existing directory", async () => { - await fs.mkdir("/dir"); - await fs.chmod("/dir", 0o700); - }); - - it("should throw ENOENT for non-existent path", async () => { - await expect(fs.chmod("/nonexistent", 0o755)).rejects.toThrow("ENOENT"); - }); - - it("should accept various mode values", async () => { - await fs.writeFile("/file.txt", "content"); - await fs.chmod("/file.txt", 0o644); - await fs.chmod("/file.txt", 0o777); - await fs.chmod("/file.txt", 0o000); - }); - }); - - describe("resolvePath", () => { - it("should resolve absolute paths", () => { - expect(fs.resolvePath("/base", "/absolute")).toBe("/absolute"); - }); - - it("should resolve relative paths", () => { - expect(fs.resolvePath("/base/dir", "file.txt")).toBe( - "/base/dir/file.txt", - ); - }); - - it("should handle .. in paths", () => { - expect(fs.resolvePath("/base/dir", "../file.txt")).toBe("/base/file.txt"); - }); - - it("should handle . in paths", () => { - expect(fs.resolvePath("/base/dir", "./file.txt")).toBe( - "/base/dir/file.txt", - ); - }); - - it("should handle multiple .. in paths", () => { - expect(fs.resolvePath("/a/b/c/d", "../../file.txt")).toBe( - "/a/b/file.txt", - ); - }); - - it("should not go above root", () => { - expect(fs.resolvePath("/base", "../../../file.txt")).toBe("/file.txt"); - }); - - it("should handle root base", () => { - expect(fs.resolvePath("/", "file.txt")).toBe("/file.txt"); - }); - - it("should resolve empty relative path", () => { - expect(fs.resolvePath("/base/dir", "")).toBe("/base/dir"); - }); - }); - - describe("getAllPaths", () => { - it("should return empty array (no tracking)", async () => { - await fs.writeFile("/file1.txt", "a"); - await fs.mkdir("/dir"); - // getAllPaths returns empty since we don't track paths - const paths = fs.getAllPaths(); - expect(paths).toEqual([]); - }); - }); - - describe("path normalization", () => { - it("should handle trailing slashes", async () => { - await fs.writeFile("/dir/file.txt", "content"); - const content = await fs.readFile("/dir/file.txt/"); - expect(content).toBe("content"); - }); - - it("should handle double slashes", async () => { - await fs.writeFile("/dir/file.txt", "content"); - const content = await fs.readFile("/dir//file.txt"); - expect(content).toBe("content"); - }); - - it("should handle . in path", async () => { - await fs.writeFile("/dir/file.txt", "content"); - const content = await fs.readFile("/dir/./file.txt"); - expect(content).toBe("content"); - }); - - it("should handle .. in path", async () => { - await fs.writeFile("/dir/file.txt", "content"); - const content = await fs.readFile("/dir/subdir/../file.txt"); - expect(content).toBe("content"); - }); - }); - - describe("mount point handling", () => { - it("should work with custom mount point", async () => { - const customFs = new AgentFsWrapper({ - fs: agentHandle, - mountPoint: "/mnt/data", - }); - await customFs.writeFile("/mnt/data/file.txt", "content"); - const content = await customFs.readFile("/mnt/data/file.txt"); - expect(content).toBe("content"); - }); - }); -}); diff --git a/sdk/typescript/src/integrations/just-bash/AgentFs.ts b/sdk/typescript/src/integrations/just-bash/AgentFs.ts deleted file mode 100644 index cb5a8742..00000000 --- a/sdk/typescript/src/integrations/just-bash/AgentFs.ts +++ /dev/null @@ -1,561 +0,0 @@ -/** - * AgentFs - Read-write filesystem backed by any AgentFS FileSystem implementation - * - * Full read-write filesystem that persists to a SQLite database. - * Designed for AI agents needing persistent, auditable file storage. - * - * Supports any FileSystem implementation: - * - AgentFS (Turso/libsql) for Node.js and browser - * - Cloudflare AgentFS (Durable Objects) for Cloudflare Workers - * - * @see https://docs.turso.tech/agentfs/sdk/typescript - */ - -import type { FileSystem } from "../../filesystem/interface.js"; -import { AgentFS, type AgentFSOptions } from "../../index_node.js"; -import type { - BufferEncoding, - CpOptions, - FsStat, - IFileSystem, - MkdirOptions, - RmOptions, -} from "just-bash"; - -/** Options for reading files */ -interface ReadFileOptions { - encoding?: BufferEncoding | null; -} - -/** Options for writing files */ -interface WriteFileOptions { - encoding?: BufferEncoding; -} - -// Text encoder/decoder for encoding conversions -const textEncoder = new TextEncoder(); -const textDecoder = new TextDecoder(); - -type FileContent = string | Uint8Array; - -/** - * Helper to convert content to Uint8Array - */ -function toBuffer(content: FileContent, encoding?: BufferEncoding): Uint8Array { - if (content instanceof Uint8Array) { - return content; - } - - switch (encoding) { - case "base64": - return Uint8Array.from(atob(content), (c) => c.charCodeAt(0)); - case "hex": { - const bytes = new Uint8Array(content.length / 2); - for (let i = 0; i < content.length; i += 2) { - bytes[i / 2] = parseInt(content.slice(i, i + 2), 16); - } - return bytes; - } - case "binary": - case "latin1": - return Uint8Array.from(content, (c) => c.charCodeAt(0)); - default: - return textEncoder.encode(content); - } -} - -/** - * Helper to convert Uint8Array to string with encoding - */ -function fromBuffer( - buffer: Uint8Array, - encoding?: BufferEncoding | null, -): string { - switch (encoding) { - case "base64": - return btoa(String.fromCharCode(...buffer)); - case "hex": - return Array.from(buffer) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); - case "binary": - case "latin1": - return String.fromCharCode(...buffer); - default: - return textDecoder.decode(buffer); - } -} - -function getEncoding( - options?: ReadFileOptions | WriteFileOptions | BufferEncoding | null, -): BufferEncoding | undefined { - if (options === null || options === undefined) { - return undefined; - } - if (typeof options === "string") { - return options as BufferEncoding; - } - return options.encoding ?? undefined; -} - -/** - * Handle to any FileSystem implementation. - * Can be from AgentFS.open() (Turso) or AgentFS.create() (Cloudflare). - */ -export interface AgentFsHandle { - fs: FileSystem; -} - -export interface AgentFsOptions { - /** - * The AgentFS handle containing a FileSystem implementation. - */ - fs: AgentFsHandle; - - /** - * The virtual mount point for the filesystem. - * Defaults to "/". - */ - mountPoint?: string; -} - -/** Default mount point for AgentFs */ -const DEFAULT_MOUNT_POINT = "/"; - -export class AgentFsWrapper implements IFileSystem { - private readonly agentFs: FileSystem; - private readonly mountPoint: string; - - constructor(options: AgentFsOptions) { - this.agentFs = options.fs.fs; - - // Normalize mount point - const mp = options.mountPoint ?? DEFAULT_MOUNT_POINT; - this.mountPoint = mp === "/" ? "/" : mp.replace(/\/+$/, ""); - if (!this.mountPoint.startsWith("/")) { - throw new Error(`Mount point must be an absolute path: ${mp}`); - } - } - - /** - * Get the mount point for this filesystem - */ - getMountPoint(): string { - return this.mountPoint; - } - - /** - * Normalize a virtual path (resolve . and .., ensure starts with /) - */ - private normalizePath(path: string): string { - if (!path || path === "/") return "/"; - - let normalized = - path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path; - - if (!normalized.startsWith("/")) { - normalized = `/${normalized}`; - } - - const parts = normalized.split("/").filter((p) => p && p !== "."); - const resolved: string[] = []; - - for (const part of parts) { - if (part === "..") { - resolved.pop(); - } else { - resolved.push(part); - } - } - - return `/${resolved.join("/")}` || "/"; - } - - /** - * Convert virtual path to AgentFS path (strip mount point prefix) - */ - private toAgentPath(virtualPath: string): string { - const normalized = this.normalizePath(virtualPath); - - if (this.mountPoint === "/") { - return normalized; - } - - if (normalized === this.mountPoint) { - return "/"; - } - - if (normalized.startsWith(`${this.mountPoint}/`)) { - return normalized.slice(this.mountPoint.length); - } - - // Path is outside mount point - return normalized; - } - - private dirname(path: string): string { - const normalized = this.normalizePath(path); - if (normalized === "/") return "/"; - const lastSlash = normalized.lastIndexOf("/"); - return lastSlash === 0 ? "/" : normalized.slice(0, lastSlash); - } - - async readFile( - path: string, - options?: ReadFileOptions | BufferEncoding, - ): Promise { - const buffer = await this.readFileBuffer(path); - const encoding = getEncoding(options); - return fromBuffer(buffer, encoding); - } - - async readFileBuffer(path: string): Promise { - const normalized = this.normalizePath(path); - const agentPath = this.toAgentPath(normalized); - - try { - const data = await this.agentFs.readFile(agentPath); - if (typeof data === "string") { - return textEncoder.encode(data); - } - return new Uint8Array(data); - } catch (e) { - const msg = e instanceof Error ? e.message : String(e); - if (msg.includes("not found") || msg.includes("ENOENT")) { - throw new Error(`ENOENT: no such file or directory, open '${path}'`); - } - throw e; - } - } - - async writeFile( - path: string, - content: FileContent, - options?: WriteFileOptions | BufferEncoding, - ): Promise { - const normalized = this.normalizePath(path); - const encoding = getEncoding(options); - const buffer = toBuffer(content, encoding); - const agentPath = this.toAgentPath(normalized); - // AgentFS creates parent directories implicitly - await this.agentFs.writeFile(agentPath, Buffer.from(buffer)); - } - - async appendFile( - path: string, - content: FileContent, - options?: WriteFileOptions | BufferEncoding, - ): Promise { - const normalized = this.normalizePath(path); - const encoding = getEncoding(options); - const newBuffer = toBuffer(content, encoding); - - // Try to read existing content - let existingBuffer: Uint8Array; - try { - existingBuffer = await this.readFileBuffer(normalized); - } catch { - existingBuffer = new Uint8Array(0); - } - - const combined = new Uint8Array(existingBuffer.length + newBuffer.length); - combined.set(existingBuffer); - combined.set(newBuffer, existingBuffer.length); - - await this.writeFile(normalized, combined); - } - - async exists(path: string): Promise { - const normalized = this.normalizePath(path); - const agentPath = this.toAgentPath(normalized); - try { - await this.agentFs.access(agentPath); - return true; - } catch { - return false; - } - } - - async stat(path: string): Promise { - const normalized = this.normalizePath(path); - const agentPath = this.toAgentPath(normalized); - - try { - const stats = await this.agentFs.stat(agentPath); - return { - isFile: stats.isFile(), - isDirectory: stats.isDirectory(), - isSymbolicLink: stats.isSymbolicLink(), - mode: stats.mode, - size: stats.size, - mtime: new Date(stats.mtime * 1000), - }; - } catch (e) { - const msg = e instanceof Error ? e.message : String(e); - if (msg.includes("not found") || msg.includes("ENOENT")) { - throw new Error(`ENOENT: no such file or directory, stat '${path}'`); - } - throw e; - } - } - - async lstat(path: string): Promise { - // AgentFS stat doesn't follow symlinks by default - return this.stat(path); - } - - async mkdir(path: string, options?: MkdirOptions): Promise { - const normalized = this.normalizePath(path); - const agentPath = this.toAgentPath(normalized); - - if (options?.recursive) { - // Create parent directories first - const parent = this.dirname(normalized); - if (parent !== "/" && parent !== normalized) { - const parentExists = await this.exists(parent); - if (!parentExists) { - await this.mkdir(parent, { recursive: true }); - } - } - } - - try { - await this.agentFs.mkdir(agentPath); - } catch (e) { - const msg = e instanceof Error ? e.message : String(e); - if (msg.includes("EEXIST") || msg.includes("already exists")) { - if (!options?.recursive) { - throw new Error(`EEXIST: file already exists, mkdir '${path}'`); - } - // With recursive, existing dir is ok - return; - } - if (msg.includes("ENOENT") || msg.includes("not found")) { - throw new Error(`ENOENT: no such file or directory, mkdir '${path}'`); - } - throw e; - } - } - - async readdir(path: string): Promise { - const normalized = this.normalizePath(path); - const agentPath = this.toAgentPath(normalized); - - try { - const entries = await this.agentFs.readdir(agentPath); - return entries.sort(); - } catch (e) { - const msg = e instanceof Error ? e.message : String(e); - if (msg.includes("not found") || msg.includes("ENOENT")) { - throw new Error(`ENOENT: no such file or directory, scandir '${path}'`); - } - throw e; - } - } - - async rm(path: string, options?: RmOptions): Promise { - const normalized = this.normalizePath(path); - const agentPath = this.toAgentPath(normalized); - - try { - await this.agentFs.rm(agentPath, { - force: options?.force, - recursive: options?.recursive, - }); - } catch (e) { - const msg = e instanceof Error ? e.message : String(e); - if (msg.includes("ENOENT") || msg.includes("not found")) { - if (!options?.force) { - throw new Error(`ENOENT: no such file or directory, rm '${path}'`); - } - return; - } - if (msg.includes("ENOTEMPTY") || msg.includes("not empty")) { - throw new Error(`ENOTEMPTY: directory not empty, rm '${path}'`); - } - if (msg.includes("EISDIR")) { - // Directory without recursive - try rmdir for empty directories - try { - await this.agentFs.rmdir(agentPath); - return; - } catch (rmdirErr) { - const rmdirMsg = - rmdirErr instanceof Error ? rmdirErr.message : String(rmdirErr); - if ( - rmdirMsg.includes("ENOTEMPTY") || - rmdirMsg.includes("not empty") - ) { - throw new Error(`ENOTEMPTY: directory not empty, rm '${path}'`); - } - throw rmdirErr; - } - } - throw e; - } - } - - async cp(src: string, dest: string, options?: CpOptions): Promise { - const srcNorm = this.normalizePath(src); - const destNorm = this.normalizePath(dest); - const srcAgent = this.toAgentPath(srcNorm); - const destAgent = this.toAgentPath(destNorm); - - const srcStat = await this.stat(srcNorm); - - if (srcStat.isFile) { - // Use native copyFile for files - await this.agentFs.copyFile(srcAgent, destAgent); - } else if (srcStat.isDirectory) { - if (!options?.recursive) { - throw new Error(`EISDIR: is a directory, cp '${src}'`); - } - // Recursively copy directory - await this.mkdir(destNorm, { recursive: true }); - const children = await this.readdir(srcNorm); - for (const child of children) { - const srcChild = srcNorm === "/" ? `/${child}` : `${srcNorm}/${child}`; - const destChild = - destNorm === "/" ? `/${child}` : `${destNorm}/${child}`; - await this.cp(srcChild, destChild, options); - } - } - } - - async mv(src: string, dest: string): Promise { - const srcNorm = this.normalizePath(src); - const destNorm = this.normalizePath(dest); - const srcAgent = this.toAgentPath(srcNorm); - const destAgent = this.toAgentPath(destNorm); - - try { - await this.agentFs.rename(srcAgent, destAgent); - } catch (e) { - const msg = e instanceof Error ? e.message : String(e); - if (msg.includes("ENOENT") || msg.includes("not found")) { - throw new Error(`ENOENT: no such file or directory, mv '${src}'`); - } - throw e; - } - } - - resolvePath(base: string, path: string): string { - if (path.startsWith("/")) { - return this.normalizePath(path); - } - const combined = base === "/" ? `/${path}` : `${base}/${path}`; - return this.normalizePath(combined); - } - - getAllPaths(): string[] { - // AgentFS doesn't provide a way to list all paths efficiently - return []; - } - - async chmod(path: string, mode: number): Promise { - const normalized = this.normalizePath(path); - - // Verify path exists - const pathExists = await this.exists(normalized); - if (!pathExists) { - throw new Error(`ENOENT: no such file or directory, chmod '${path}'`); - } - - // AgentFS doesn't support chmod yet - this is a no-op - void mode; - } - - async symlink(target: string, linkPath: string): Promise { - const normalized = this.normalizePath(linkPath); - - const pathExists = await this.exists(normalized); - if (pathExists) { - throw new Error(`EEXIST: file already exists, symlink '${linkPath}'`); - } - - // Use JSON workaround for symlinks - this provides consistent behavior - // across all FileSystem implementations and allows reading symlink content - const content = JSON.stringify({ __symlink: target }); - await this.writeFile(normalized, content); - } - - async link(existingPath: string, newPath: string): Promise { - const existingNorm = this.normalizePath(existingPath); - const newNorm = this.normalizePath(newPath); - const existingAgent = this.toAgentPath(existingNorm); - const newAgent = this.toAgentPath(newNorm); - - const existingStat = await this.stat(existingNorm); - if (!existingStat.isFile) { - throw new Error(`EPERM: operation not permitted, link '${existingPath}'`); - } - - const newExists = await this.exists(newNorm); - if (newExists) { - throw new Error(`EEXIST: file already exists, link '${newPath}'`); - } - - // Use copyFile for hard link emulation - await this.agentFs.copyFile(existingAgent, newAgent); - } - - async readlink(path: string): Promise { - const normalized = this.normalizePath(path); - - const pathExists = await this.exists(normalized); - if (!pathExists) { - throw new Error(`ENOENT: no such file or directory, readlink '${path}'`); - } - - // Read as JSON symlink workaround - try { - const content = await this.readFile(normalized); - const parsed = JSON.parse(content); - if (parsed.__symlink) { - return parsed.__symlink; - } - } catch { - // Not a JSON symlink - } - - throw new Error(`EINVAL: invalid argument, readlink '${path}'`); - } -} - -/** - * Create a just-bash compatible filesystem backed by any AgentFS FileSystem. - * - * @example Node.js with Turso: - * ```ts - * import { agentfs } from "agentfs-sdk/just-bash"; - * - * const fs = await agentfs({ path: "agent.db" }); - * const bashTool = createBashTool({ fs }); - * ``` - * - * @example Cloudflare Workers: - * ```ts - * import { agentfs } from "agentfs-sdk/just-bash"; - * import { AgentFS } from "agentfs-sdk/cloudflare"; - * - * // In a Durable Object: - * const agentFs = AgentFS.create(ctx.storage); - * const fs = agentfs({ fs: agentFs }); - * const bashTool = createBashTool({ fs }); - * ``` - */ -export async function agentfs( - handleOrOptions: AgentFsHandle | FileSystem | AgentFSOptions, - mountPoint?: string -): Promise { - // Check if it's a FileSystem directly (has stat method but not a handle) - if (typeof (handleOrOptions as FileSystem).stat === 'function' && !('fs' in handleOrOptions)) { - return new AgentFsWrapper({ fs: { fs: handleOrOptions as FileSystem }, mountPoint }); - } - // Check if it's an AgentFsHandle (has fs property containing a FileSystem) - if ("fs" in handleOrOptions && typeof (handleOrOptions as AgentFsHandle).fs?.stat === 'function') { - return new AgentFsWrapper({ fs: handleOrOptions as AgentFsHandle, mountPoint }); - } - // Otherwise it's AgentFSOptions for creating a new Turso-based AgentFS - const handle = await AgentFS.open(handleOrOptions as AgentFSOptions); - return new AgentFsWrapper({ fs: handle, mountPoint }); -} diff --git a/sdk/typescript/src/integrations/just-bash/index.ts b/sdk/typescript/src/integrations/just-bash/index.ts deleted file mode 100644 index 13037546..00000000 --- a/sdk/typescript/src/integrations/just-bash/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { agentfs, AgentFsWrapper, type AgentFsHandle, type AgentFsOptions } from "./AgentFs.js"; -export type { FileSystem } from "../../filesystem/interface.js"; diff --git a/sdk/typescript/src/integrations/serverless/adapter.ts b/sdk/typescript/src/integrations/serverless/adapter.ts deleted file mode 100644 index a89b98c8..00000000 --- a/sdk/typescript/src/integrations/serverless/adapter.ts +++ /dev/null @@ -1,196 +0,0 @@ -/** - * Adapter that wraps @tursodatabase/serverless Connection to match - * the DatabasePromise interface used by AgentFS internals. - * - * The core challenge: DatabasePromise.prepare() is synchronous and returns - * a Statement immediately, but serverless Connection.prepare() is async - * (it needs to fetch column metadata over HTTP). - * - * Solution: return a LazyStatement that defers the actual prepare() call - * until run()/get()/all() is called — those are already async, so the - * deferral is invisible to callers. - * - * Note on parameter passing: DatabasePromise's Statement uses rest params - * (run(...args)), while serverless Statement uses a single array param - * (run(args?)). The adapter collects rest params and forwards them as a - * single array — this is correct and tested. - */ - -import type { Connection, Statement as ServerlessStatement } from "@tursodatabase/serverless"; -import type { DatabasePromise } from "@tursodatabase/database-common"; - -/** - * A statement that defers the async prepare() call until execution. - * - * DatabasePromise.prepare() must return synchronously, but the serverless - * driver's prepare() is async. LazyStatement bridges this by storing the - * SQL and only calling conn.prepare() when run/get/all is invoked. - * - * The statement promise is cached for performance — options (raw, pluck, - * safeIntegers) are applied at execution time on the resolved statement. - */ -class LazyStatement { - private conn: Connection; - private sql: string; - private stmtPromise: Promise | null = null; - private _raw = false; - private _pluck = false; - private _safeIntegers = false; - - constructor(conn: Connection, sql: string) { - this.conn = conn; - this.sql = sql; - } - - private async getStmt(): Promise { - if (!this.stmtPromise) { - this.stmtPromise = this.conn.prepare(this.sql); - } - const stmt = await this.stmtPromise; - // Apply options at execution time so they reflect the current state - stmt.raw(this._raw); - stmt.pluck(this._pluck); - stmt.safeIntegers(this._safeIntegers); - return stmt; - } - - raw(raw?: boolean): this { - this._raw = raw !== false; - return this; - } - - pluck(pluck?: boolean): this { - this._pluck = pluck !== false; - return this; - } - - safeIntegers(toggle?: boolean): this { - this._safeIntegers = toggle !== false; - return this; - } - - columns(): any[] { - throw new Error("columns() requires an async prepare — not supported synchronously in serverless mode"); - } - - get source(): void { - return undefined; - } - - get reader(): void { - return undefined; - } - - get database(): any { - return undefined; - } - - // Note: DatabasePromise Statement uses rest params (...args), - // but serverless Statement uses a single array param (args?). - // We collect rest params and forward as a single array. - - async run(...args: any[]): Promise<{ changes: number; lastInsertRowid: number }> { - const stmt = await this.getStmt(); - return stmt.run(args); - } - - async get(...args: any[]): Promise { - const stmt = await this.getStmt(); - return stmt.get(args); - } - - async all(...args: any[]): Promise { - const stmt = await this.getStmt(); - return stmt.all(args); - } - - async *iterate(...args: any[]): AsyncGenerator { - const stmt = await this.getStmt(); - yield* stmt.iterate(args); - } - - bind(..._args: any[]): this { - throw new Error("bind() is not supported in serverless mode — pass parameters to run/get/all instead"); - } - - interrupt(): void {} - - close(): void {} -} - -function notSupported(name: string): () => never { - return () => { - throw new Error(`${name}() is not supported in serverless mode`); - }; -} - -/** - * Wraps a @tursodatabase/serverless Connection to match - * the DatabasePromise interface expected by AgentFS.openWith(). - * - * @example - * ```typescript - * import { connect } from "@tursodatabase/serverless"; - * import { AgentFS } from "agentfs-sdk"; - * import { createServerlessAdapter } from "agentfs-sdk/serverless"; - * - * const conn = connect({ - * url: "http://localhost:8080", - * }); - * - * const db = createServerlessAdapter(conn); - * const agent = await AgentFS.openWith(db); - * ``` - */ -export function createServerlessAdapter(conn: Connection): DatabasePromise { - return { - name: "serverless", - readonly: false, - open: true, - memory: false, - inTransaction: false, - - async connect(): Promise { - // Validate the connection by running a simple query - await conn.execute("SELECT 1"); - }, - - prepare(sql: string): LazyStatement { - return new LazyStatement(conn, sql); - }, - - // DatabasePromise.transaction() returns a wrapper function that - // executes fn inside a transaction when called. The serverless - // driver's transaction() has the same shape. - transaction(fn: (...args: any[]) => Promise) { - return (...bindParameters: any[]) => { - return conn.transaction(async () => { - return fn(...bindParameters); - }); - }; - }, - - async exec(sql: string): Promise { - await conn.exec(sql); - }, - - pragma(): Promise { - throw new Error("pragma() is not supported in serverless mode — pragmas are not available over HTTP"); - }, - - backup: notSupported("backup"), - serialize: notSupported("serialize"), - function: notSupported("function"), - aggregate: notSupported("aggregate"), - table: notSupported("table"), - loadExtension: notSupported("loadExtension"), - maxWriteReplicationIndex: notSupported("maxWriteReplicationIndex"), - interrupt: notSupported("interrupt"), - - defaultSafeIntegers(_toggle?: boolean) {}, - - async close(): Promise { - await conn.close(); - }, - } as unknown as DatabasePromise; -} diff --git a/sdk/typescript/src/integrations/serverless/index.ts b/sdk/typescript/src/integrations/serverless/index.ts deleted file mode 100644 index 3419257e..00000000 --- a/sdk/typescript/src/integrations/serverless/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Serverless integration for AgentFS. - * - * Provides an adapter that wraps `@tursodatabase/serverless` Connection - * to be compatible with AgentFS's DatabasePromise interface, enabling - * AgentFS to work with remote Turso databases over HTTP. - * - * @example - * ```typescript - * import { connect } from "@tursodatabase/serverless"; - * import { AgentFS } from "agentfs-sdk"; - * import { createServerlessAdapter } from "agentfs-sdk/serverless"; - * - * const conn = connect({ - * url: process.env.TURSO_DATABASE_URL!, - * authToken: process.env.TURSO_AUTH_TOKEN, - * }); - * - * const db = createServerlessAdapter(conn); - * const agent = await AgentFS.openWith(db); - * - * await agent.fs.writeFile("/hello.txt", "Hello from Turso Cloud!"); - * const content = await agent.fs.readFile("/hello.txt", "utf8"); - * console.log(content); - * - * await agent.close(); - * ``` - * - * @see https://github.com/tursodatabase/agentfs/issues/156 - */ - -export { createServerlessAdapter } from "./adapter.js"; diff --git a/sdk/typescript/src/kvstore.ts b/sdk/typescript/src/kvstore.ts deleted file mode 100644 index 82500be0..00000000 --- a/sdk/typescript/src/kvstore.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { DatabasePromise } from '@tursodatabase/database-common'; - -export class KvStore { - private db: DatabasePromise; - - private constructor(db: DatabasePromise) { - this.db = db; - } - - /** - * Create a KvStore from an existing database connection - */ - static async fromDatabase(db: DatabasePromise): Promise { - const kv = new KvStore(db); - await kv.initialize(); - return kv; - } - - private async initialize(): Promise { - // Create the key-value store table if it doesn't exist - await this.db.exec(` - CREATE TABLE IF NOT EXISTS kv_store ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - created_at INTEGER DEFAULT (unixepoch()), - updated_at INTEGER DEFAULT (unixepoch()) - ) - `); - - // Create index on created_at for potential queries - await this.db.exec(` - CREATE INDEX IF NOT EXISTS idx_kv_store_created_at - ON kv_store(created_at) - `); - } - - async set(key: string, value: any): Promise { - // Serialize the value to JSON - const serializedValue = JSON.stringify(value); - - // Use prepared statement to insert or update - const stmt = this.db.prepare(` - INSERT INTO kv_store (key, value, updated_at) - VALUES (?, ?, unixepoch()) - ON CONFLICT(key) DO UPDATE SET - value = excluded.value, - updated_at = unixepoch() - `); - - await stmt.run(key, serializedValue); - } - - async get(key: string): Promise { - const stmt = this.db.prepare(`SELECT value FROM kv_store WHERE key = ?`); - const row = await stmt.get(key) as { value: string } | undefined; - - if (!row) { - return undefined; - } - - // Deserialize the JSON value - return JSON.parse(row.value) as T; - } - - async list(prefix: string): Promise<{ key: string, value: any }[]> { - const stmt = this.db.prepare(`SELECT key, value FROM kv_store WHERE key LIKE ? ESCAPE '\\'`); - const escaped = prefix.replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_'); - const rows = await stmt.all(escaped + '%') as { key: string, value: string }[]; - return rows.map(r => ({ key: r.key, value: JSON.parse(r.value) })); - } - - async delete(key: string): Promise { - const stmt = this.db.prepare(`DELETE FROM kv_store WHERE key = ?`); - await stmt.run(key); - } -} diff --git a/sdk/typescript/src/toolcalls.ts b/sdk/typescript/src/toolcalls.ts deleted file mode 100644 index c80050b7..00000000 --- a/sdk/typescript/src/toolcalls.ts +++ /dev/null @@ -1,256 +0,0 @@ -import type { DatabasePromise } from '@tursodatabase/database-common'; - -export interface ToolCall { - id: number; - name: string; - parameters?: any; - result?: any; - error?: string; - status: 'pending' | 'success' | 'error'; - started_at: number; - completed_at?: number; - duration_ms?: number; -} - -export interface ToolCallStats { - name: string; - total_calls: number; - successful: number; - failed: number; - avg_duration_ms: number; -} - -export class ToolCalls { - private db: DatabasePromise; - - private constructor(db: DatabasePromise) { - this.db = db; - } - - /** - * Create a ToolCalls from an existing database connection - */ - static async fromDatabase(db: DatabasePromise): Promise { - const tools = new ToolCalls(db); - await tools.initialize(); - return tools; - } - - private async initialize(): Promise { - // Create the tool_calls table - await this.db.exec(` - CREATE TABLE IF NOT EXISTS tool_calls ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - parameters TEXT, - result TEXT, - error TEXT, - status TEXT NOT NULL DEFAULT 'pending', - started_at INTEGER NOT NULL, - completed_at INTEGER, - duration_ms INTEGER - ) - `); - - // Create indexes for efficient queries - await this.db.exec(` - CREATE INDEX IF NOT EXISTS idx_tool_calls_name - ON tool_calls(name) - `); - - await this.db.exec(` - CREATE INDEX IF NOT EXISTS idx_tool_calls_started_at - ON tool_calls(started_at) - `); - } - - /** - * Start a new tool call and mark it as pending - * Returns the ID of the created tool call record - */ - async start(name: string, parameters?: any): Promise { - const serializedParams = parameters !== undefined ? JSON.stringify(parameters) : null; - const started_at = Math.floor(Date.now() / 1000); - - const stmt = this.db.prepare(` - INSERT INTO tool_calls (name, parameters, status, started_at) - VALUES (?, ?, 'pending', ?) - RETURNING id - `); - - const { id } = await stmt.get(name, serializedParams, started_at); - return Number(id); - } - - /** - * Mark a tool call as successful - */ - async success(id: number, result?: any): Promise { - const serializedResult = result !== undefined ? JSON.stringify(result) : null; - const completed_at = Math.floor(Date.now() / 1000); - - // Get the started_at time to calculate duration - const getStmt = this.db.prepare('SELECT started_at FROM tool_calls WHERE id = ?'); - const row = await getStmt.get(id) as { started_at: number } | undefined; - - if (!row) { - throw new Error(`Tool call with ID ${id} not found`); - } - - const duration_ms = (completed_at - row.started_at) * 1000; - - const updateStmt = this.db.prepare(` - UPDATE tool_calls - SET status = 'success', result = ?, completed_at = ?, duration_ms = ? - WHERE id = ? - `); - - await updateStmt.run(serializedResult, completed_at, duration_ms, id); - } - - /** - * Mark a tool call as failed - */ - async error(id: number, error: string): Promise { - const completed_at = Math.floor(Date.now() / 1000); - - // Get the started_at time to calculate duration - const getStmt = this.db.prepare('SELECT started_at FROM tool_calls WHERE id = ?'); - const row = await getStmt.get(id) as { started_at: number } | undefined; - - if (!row) { - throw new Error(`Tool call with ID ${id} not found`); - } - - const duration_ms = (completed_at - row.started_at) * 1000; - - const updateStmt = this.db.prepare(` - UPDATE tool_calls - SET status = 'error', error = ?, completed_at = ?, duration_ms = ? - WHERE id = ? - `); - - await updateStmt.run(error, completed_at, duration_ms, id); - } - - /** - * Record a completed tool call - * Either result or error should be provided, not both - * Returns the ID of the created tool call record - */ - async record( - name: string, - started_at: number, - completed_at: number, - parameters?: any, - result?: any, - error?: string - ): Promise { - const serializedParams = parameters !== undefined ? JSON.stringify(parameters) : null; - const serializedResult = result !== undefined ? JSON.stringify(result) : null; - const duration_ms = (completed_at - started_at) * 1000; - const status = error ? 'error' : 'success'; - - const stmt = this.db.prepare(` - INSERT INTO tool_calls (name, parameters, result, error, status, started_at, completed_at, duration_ms) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - RETURNING id - `); - - const { id } = await stmt.get(name, serializedParams, serializedResult, error || null, status, started_at, completed_at, duration_ms); - return Number(id); - } - - /** - * Get a specific tool call by ID - */ - async get(id: number): Promise { - const stmt = this.db.prepare(` - SELECT * FROM tool_calls WHERE id = ? - `); - - const row = await stmt.get(id) as any; - if (!row) { - return undefined; - } - - return this.rowToToolCall(row); - } - - /** - * Query tool calls by name - */ - async getByName(name: string, limit?: number): Promise { - const limitClause = limit !== undefined ? `LIMIT ${limit}` : ''; - const stmt = this.db.prepare(` - SELECT * FROM tool_calls - WHERE name = ? - ORDER BY started_at DESC - ${limitClause} - `); - - const rows = await stmt.all(name) as any[]; - return rows.map(row => this.rowToToolCall(row)); - } - - /** - * Query recent tool calls - */ - async getRecent(since: number, limit?: number): Promise { - const limitClause = limit !== undefined ? `LIMIT ${limit}` : ''; - const stmt = this.db.prepare(` - SELECT * FROM tool_calls - WHERE started_at > ? - ORDER BY started_at DESC - ${limitClause} - `); - - const rows = await stmt.all(since) as any[]; - return rows.map(row => this.rowToToolCall(row)); - } - - /** - * Get performance statistics for all tools - * Only includes completed calls (success or failed), not pending ones - */ - async getStats(): Promise { - const stmt = this.db.prepare(` - SELECT - name, - COUNT(*) as total_calls, - SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successful, - SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as failed, - AVG(duration_ms) as avg_duration_ms - FROM tool_calls - WHERE status != 'pending' - GROUP BY name - ORDER BY total_calls DESC - `); - - const rows = await stmt.all() as any[]; - return rows.map(row => ({ - name: row.name, - total_calls: row.total_calls, - successful: row.successful, - failed: row.failed, - avg_duration_ms: row.avg_duration_ms || 0, - })); - } - - /** - * Helper to convert database row to ToolCall object - */ - private rowToToolCall(row: any): ToolCall { - return { - id: row.id, - name: row.name, - parameters: row.parameters !== null ? JSON.parse(row.parameters) : undefined, - result: row.result !== null ? JSON.parse(row.result) : undefined, - error: row.error !== null ? row.error : undefined, - status: row.status as 'pending' | 'success' | 'error', - started_at: row.started_at, - completed_at: row.completed_at !== null ? row.completed_at : undefined, - duration_ms: row.duration_ms !== null ? row.duration_ms : undefined, - }; - } -} diff --git a/sdk/typescript/tests/filesystem.test.ts b/sdk/typescript/tests/filesystem.test.ts deleted file mode 100644 index 2dd6f7c1..00000000 --- a/sdk/typescript/tests/filesystem.test.ts +++ /dev/null @@ -1,1026 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { Database } from "@tursodatabase/database"; -import { AgentFS as Filesystem } from "../src/filesystem/agentfs.js"; -import { mkdtempSync, rmSync } from "fs"; -import { tmpdir } from "os"; -import { join } from "path"; - -describe("Filesystem Integration Tests", () => { - let db: Database; - let fs: Filesystem; - let tempDir: string; - let dbPath: string; - - beforeEach(async () => { - // Create temporary directory for test database - tempDir = mkdtempSync(join(tmpdir(), "agentfs-test-")); - dbPath = join(tempDir, "test.db"); - - // Initialize database and Filesystem - db = new Database(dbPath); - await db.connect(); - // sync uses CDC so we must ensure that AgentFS components works properly with this extra setup - await db.exec("PRAGMA unstable_capture_data_changes_conn('full')"); - fs = await Filesystem.fromDatabase(db); - }); - - afterEach(async () => { - // Close database before cleaning up - try { - await db.close(); - } catch { - // Ignore close errors - } - // Clean up temporary directories - try { - rmSync(tempDir, { recursive: true, force: true }); - } catch { - // Ignore cleanup errors - } - }); - - describe("File Write Operations", () => { - it("should write and read a simple text file", async () => { - await fs.writeFile("/test.txt", "Hello, World!"); - const content = await fs.readFile("/test.txt", "utf8"); - expect(content).toBe("Hello, World!"); - }); - - it('should respect encoding option when writing string data', async () => { - await fs.writeFile('/enc.txt', 'hello', { encoding: 'utf16le' }); - const data = await fs.readFile('/enc.txt') as Buffer; - expect(data.equals(Buffer.from('hello', 'utf16le'))).toBe(true); - }); - - it('should throw EISDIR when attempting to write to a directory path', async () => { - await fs.writeFile('/dir/file.txt', 'content'); // create directory - await expect(fs.writeFile('/dir', 'nope')).rejects.toMatchObject({ code: 'EISDIR' }); - }); - - it('should throw ENOTDIR when a parent path component is a file', async () => { - await fs.writeFile('/a', 'file-content'); // create file - await expect(fs.writeFile('/a/b.txt', 'child')).rejects.toMatchObject({ code: 'ENOTDIR' }); - }); - - it('should write and read files in subdirectories', async () => { - await fs.writeFile('/dir/subdir/file.txt', 'nested content'); - const content = await fs.readFile('/dir/subdir/file.txt', 'utf8'); - expect(content).toBe('nested content'); - }); - - it("should overwrite existing file", async () => { - await fs.writeFile("/overwrite.txt", "original content"); - await fs.writeFile("/overwrite.txt", "new content"); - const content = await fs.readFile("/overwrite.txt", "utf8"); - expect(content).toBe("new content"); - }); - - it("should handle empty file content", async () => { - await fs.writeFile("/empty.txt", ""); - const content = await fs.readFile("/empty.txt", "utf8"); - expect(content).toBe(""); - }); - - it("should handle large file content", async () => { - const largeContent = "x".repeat(100000); - await fs.writeFile("/large.txt", largeContent); - const content = await fs.readFile("/large.txt", "utf8"); - expect(content).toBe(largeContent); - }); - - it("should handle files with special characters in content", async () => { - const specialContent = "Special chars: \n\t\r\"'\\"; - await fs.writeFile("/special.txt", specialContent); - const content = await fs.readFile("/special.txt", "utf8"); - expect(content).toBe(specialContent); - }); - }); - - describe('File Read Operations', () => { - it('should throw error when reading non-existent file', async () => { - await expect(fs.readFile('/non-existent.txt')).rejects.toMatchObject({ code: 'ENOENT' }); - }); - - it('should throw EISDIR when attempting to read a directory path', async () => { - await fs.writeFile('/dir/file.txt', 'content'); - await expect(fs.readFile('/dir')).rejects.toMatchObject({ code: 'EISDIR' }); - }); - - it("should read multiple different files", async () => { - await fs.writeFile("/file1.txt", "content 1"); - await fs.writeFile("/file2.txt", "content 2"); - await fs.writeFile("/file3.txt", "content 3"); - - expect(await fs.readFile("/file1.txt", "utf8")).toBe("content 1"); - expect(await fs.readFile("/file2.txt", "utf8")).toBe("content 2"); - expect(await fs.readFile("/file3.txt", "utf8")).toBe("content 3"); - }); - }); - - describe("Directory Operations", () => { - it("should create a directory with mkdir()", async () => { - await fs.mkdir("/newdir"); - const entries = await fs.readdir("/"); - expect(entries).toContain("newdir"); - }); - - it("should throw EEXIST when mkdir() is called on an existing directory", async () => { - await fs.mkdir("/exists"); - await expect(fs.mkdir("/exists")).rejects.toMatchObject({ code: "EEXIST" }); - }); - - it("should throw ENOENT when parent directory does not exist", async () => { - await expect(fs.mkdir("/missing-parent/child")).rejects.toMatchObject({ code: "ENOENT" }); - }); - - it("should list files in root directory", async () => { - await fs.writeFile("/file1.txt", "content 1"); - await fs.writeFile("/file2.txt", "content 2"); - await fs.writeFile("/file3.txt", "content 3"); - - const files = await fs.readdir("/"); - expect(files).toContain("file1.txt"); - expect(files).toContain("file2.txt"); - expect(files).toContain("file3.txt"); - expect(files).toHaveLength(3); - }); - - it("should list files in subdirectory", async () => { - await fs.writeFile("/dir/file1.txt", "content 1"); - await fs.writeFile("/dir/file2.txt", "content 2"); - await fs.writeFile("/other/file3.txt", "content 3"); - - const files = await fs.readdir("/dir"); - expect(files).toContain("file1.txt"); - expect(files).toContain("file2.txt"); - expect(files).not.toContain("file3.txt"); - expect(files).toHaveLength(2); - }); - - it("should return empty array for empty directory", async () => { - await fs.writeFile("/dir/file.txt", "content"); - // /dir exists but is not empty, root exists and should be empty except for 'dir' - const files = await fs.readdir("/"); - expect(files).toContain("dir"); - }); - - it("should distinguish between files in different directories", async () => { - await fs.writeFile("/dir1/file.txt", "content 1"); - await fs.writeFile("/dir2/file.txt", "content 2"); - - const files1 = await fs.readdir("/dir1"); - const files2 = await fs.readdir("/dir2"); - - expect(files1).toContain("file.txt"); - expect(files2).toContain("file.txt"); - expect(files1).toHaveLength(1); - expect(files2).toHaveLength(1); - }); - - it("should list subdirectories within a directory", async () => { - await fs.writeFile("/parent/child1/file.txt", "content"); - await fs.writeFile("/parent/child2/file.txt", "content"); - await fs.writeFile("/parent/file.txt", "content"); - - const entries = await fs.readdir("/parent"); - expect(entries).toContain("file.txt"); - expect(entries).toContain("child1"); - expect(entries).toContain("child2"); - }); - - it("should handle nested directory structures", async () => { - await fs.writeFile("/a/b/c/d/file.txt", "deep content"); - const files = await fs.readdir("/a/b/c/d"); - expect(files).toContain("file.txt"); - }); - - it('should throw ENOTDIR when attempting to readdir a file path', async () => { - await fs.writeFile('/notadir.txt', 'content'); - await expect(fs.readdir('/notadir.txt')).rejects.toMatchObject({ code: 'ENOTDIR' }); - }); - }); - - describe('File Delete Operations', () => { - it('should delete an existing file', async () => { - await fs.writeFile('/delete-me.txt', 'content'); - await fs.unlink('/delete-me.txt'); - await expect(fs.readFile('/delete-me.txt')).rejects.toThrow(); - }); - - it('should handle deleting non-existent file', async () => { - await expect(fs.unlink('/non-existent.txt')).rejects.toThrow('ENOENT'); - }); - - it("should delete file and update directory listing", async () => { - await fs.writeFile("/dir/file1.txt", "content 1"); - await fs.writeFile("/dir/file2.txt", "content 2"); - - await fs.unlink('/dir/file1.txt'); - - const files = await fs.readdir("/dir"); - expect(files).not.toContain("file1.txt"); - expect(files).toContain("file2.txt"); - expect(files).toHaveLength(1); - }); - - it('should allow recreating deleted file', async () => { - await fs.writeFile('/recreate.txt', 'original'); - await fs.unlink('/recreate.txt'); - await fs.writeFile('/recreate.txt', 'new content'); - const content = await fs.readFile('/recreate.txt', 'utf8'); - expect(content).toBe('new content'); - }); - - it('should throw EISDIR when attempting to unlink a directory', async () => { - await fs.writeFile('/adir/file.txt', 'content'); - await expect(fs.unlink('/adir')).rejects.toMatchObject({ code: 'EISDIR' }); - }); - }); - - describe("rm() Operations", () => { - it("should remove a file", async () => { - await fs.writeFile("/rmfile.txt", "content"); - await fs.rm("/rmfile.txt"); - await expect(fs.readFile("/rmfile.txt")).rejects.toMatchObject({ code: "ENOENT" }); - }); - - it("should not throw when force=true and path does not exist", async () => { - await expect(fs.rm("/does-not-exist", { force: true })).resolves.toBeUndefined(); - }); - - it("should throw ENOENT when force=false and path does not exist", async () => { - await expect(fs.rm("/does-not-exist")).rejects.toMatchObject({ code: "ENOENT" }); - }); - - it("should throw EISDIR when trying to rm a directory without recursive", async () => { - await fs.mkdir("/rmdir"); - await expect(fs.rm("/rmdir")).rejects.toMatchObject({ code: "EISDIR" }); - }); - - it("should remove a directory recursively", async () => { - await fs.writeFile("/tree/a/b/c.txt", "content"); - await fs.rm("/tree", { recursive: true }); - await expect(fs.readdir("/tree")).rejects.toMatchObject({ code: "ENOENT" }); - const root = await fs.readdir("/"); - expect(root).not.toContain("tree"); - }); - }); - - describe("rmdir() Operations", () => { - it("should remove an empty directory", async () => { - await fs.mkdir("/emptydir"); - await fs.rmdir("/emptydir"); - await expect(fs.readdir("/emptydir")).rejects.toMatchObject({ code: "ENOENT" }); - const root = await fs.readdir("/"); - expect(root).not.toContain("emptydir"); - }); - - it("should throw ENOTEMPTY when directory is not empty", async () => { - await fs.writeFile("/nonempty/file.txt", "content"); - await expect(fs.rmdir("/nonempty")).rejects.toMatchObject({ code: "ENOTEMPTY" }); - }); - - it("should throw ENOTDIR when path is a file", async () => { - await fs.writeFile("/afile", "content"); - await expect(fs.rmdir("/afile")).rejects.toMatchObject({ code: "ENOTDIR" }); - }); - - it("should throw EPERM when attempting to remove root", async () => { - await expect(fs.rmdir("/")).rejects.toMatchObject({ code: "EPERM" }); - }); - }); - - describe("rename() Operations", () => { - it("should rename a file", async () => { - await fs.writeFile("/a.txt", "hello"); - await fs.rename("/a.txt", "/b.txt"); - await expect(fs.readFile("/a.txt")).rejects.toMatchObject({ code: "ENOENT" }); - const content = await fs.readFile("/b.txt", "utf8"); - expect(content).toBe("hello"); - }); - - it("should rename a directory and preserve its contents", async () => { - await fs.writeFile("/olddir/sub/file.txt", "content"); - await fs.rename("/olddir", "/newdir"); - await expect(fs.readdir("/olddir")).rejects.toMatchObject({ code: "ENOENT" }); - const content = await fs.readFile("/newdir/sub/file.txt", "utf8"); - expect(content).toBe("content"); - }); - - it("should overwrite destination file if it exists", async () => { - await fs.writeFile("/src.txt", "src"); - await fs.writeFile("/dst.txt", "dst"); - await fs.rename("/src.txt", "/dst.txt"); - await expect(fs.readFile("/src.txt")).rejects.toMatchObject({ code: "ENOENT" }); - const content = await fs.readFile("/dst.txt", "utf8"); - expect(content).toBe("src"); - }); - - it("should throw EISDIR when renaming a file onto a directory", async () => { - await fs.writeFile("/dir/file.txt", "content"); - await fs.writeFile("/file.txt", "content"); - await expect(fs.rename("/file.txt", "/dir")).rejects.toMatchObject({ code: "EISDIR" }); - }); - - it("should throw ENOTDIR when renaming a directory onto a file", async () => { - await fs.mkdir("/somedir"); - await fs.writeFile("/somefile", "content"); - await expect(fs.rename("/somedir", "/somefile")).rejects.toMatchObject({ code: "ENOTDIR" }); - }); - - it("should replace an existing empty directory", async () => { - await fs.mkdir("/fromdir"); - await fs.mkdir("/todir"); - await fs.rename("/fromdir", "/todir"); - const root = await fs.readdir("/"); - expect(root).toContain("todir"); - expect(root).not.toContain("fromdir"); - await expect(fs.readdir("/fromdir")).rejects.toMatchObject({ code: "ENOENT" }); - }); - - it("should throw ENOTEMPTY when replacing a non-empty directory", async () => { - await fs.mkdir("/fromdir"); - await fs.writeFile("/todir/file.txt", "content"); - await expect(fs.rename("/fromdir", "/todir")).rejects.toMatchObject({ code: "ENOTEMPTY" }); - }); - - it("should throw EPERM when attempting to rename root", async () => { - await expect(fs.rename("/", "/x")).rejects.toMatchObject({ code: "EPERM" }); - }); - - it("should throw EINVAL when renaming a directory into its own subdirectory", async () => { - await fs.writeFile("/cycle/sub/file.txt", "content"); - await expect(fs.rename("/cycle", "/cycle/sub/moved")).rejects.toMatchObject({ code: "EINVAL" }); - }); - }); - - describe("copyFile() Operations", () => { - it("should copy a file", async () => { - await fs.writeFile("/src.txt", "hello"); - await fs.copyFile("/src.txt", "/dst.txt"); - const srcContent = await fs.readFile("/src.txt", "utf8"); - const dstContent = await fs.readFile("/dst.txt", "utf8"); - expect(srcContent).toBe("hello"); - expect(dstContent).toBe("hello"); - }); - - it("should overwrite destination if it exists", async () => { - await fs.writeFile("/src.txt", "src"); - await fs.writeFile("/dst.txt", "dst"); - await fs.copyFile("/src.txt", "/dst.txt"); - const dstContent = await fs.readFile("/dst.txt", "utf8"); - expect(dstContent).toBe("src"); - }); - - it("should throw ENOENT when source does not exist", async () => { - await expect(fs.copyFile("/nope.txt", "/out.txt")).rejects.toMatchObject({ code: "ENOENT" }); - }); - - it("should throw ENOENT when destination parent does not exist", async () => { - await fs.writeFile("/src3.txt", "content"); - await expect(fs.copyFile("/src3.txt", "/missing/child.txt")).rejects.toMatchObject({ code: "ENOENT" }); - }); - - it("should throw EISDIR when source is a directory", async () => { - await fs.mkdir("/asrcdir"); - await expect(fs.copyFile("/asrcdir", "/out2.txt")).rejects.toMatchObject({ code: "EISDIR" }); - }); - - it("should throw EISDIR when destination is a directory", async () => { - await fs.writeFile("/src4.txt", "content"); - await fs.mkdir("/adstdir"); - await expect(fs.copyFile("/src4.txt", "/adstdir")).rejects.toMatchObject({ code: "EISDIR" }); - }); - - it("should throw EINVAL when source and destination are the same", async () => { - await fs.writeFile("/same.txt", "content"); - await expect(fs.copyFile("/same.txt", "/same.txt")).rejects.toMatchObject({ code: "EINVAL" }); - }); - }); - - describe("access() Operations", () => { - it("should resolve when a file exists", async () => { - await fs.writeFile("/exists.txt", "content"); - await expect(fs.access("/exists.txt")).resolves.toBeUndefined(); - }); - - it("should resolve when a directory exists", async () => { - await fs.mkdir("/existsdir"); - await expect(fs.access("/existsdir")).resolves.toBeUndefined(); - }); - - it("should throw ENOENT when path does not exist", async () => { - await expect(fs.access("/does-not-exist")).rejects.toMatchObject({ code: "ENOENT" }); - }); - }); - - describe("Path Handling", () => { - it("should handle paths with trailing slashes", async () => { - await fs.writeFile("/dir/file.txt", "content"); - const files1 = await fs.readdir("/dir"); - const files2 = await fs.readdir("/dir/"); - expect(files1).toEqual(files2); - }); - - it("should handle paths with special characters", async () => { - const specialPath = "/dir-with-dash/file_with_underscore.txt"; - await fs.writeFile(specialPath, "content"); - const content = await fs.readFile(specialPath, "utf8"); - expect(content).toBe("content"); - }); - }); - - describe("Concurrent Operations", () => { - it("should handle concurrent writes to different files", async () => { - const operations = Array.from({ length: 10 }, (_, i) => - fs.writeFile(`/concurrent-${i}.txt`, `content ${i}`) - ); - await Promise.all(operations); - - // Verify all files were created - for (let i = 0; i < 10; i++) { - const content = await fs.readFile(`/concurrent-${i}.txt`, "utf8"); - expect(content).toBe(`content ${i}`); - } - }); - - it("should handle concurrent reads", async () => { - await fs.writeFile("/concurrent-read.txt", "shared content"); - - const results = await Promise.all( - Array.from({ length: 10 }, () => - fs.readFile("/concurrent-read.txt", "utf8") - ) - ); - - results.forEach((content) => { - expect(content).toBe("shared content"); - }); - }); - }); - - describe("File System Integrity", () => { - it("should maintain file hierarchy integrity", async () => { - await fs.writeFile("/root.txt", "root"); - await fs.writeFile("/dir1/file.txt", "dir1"); - await fs.writeFile("/dir2/file.txt", "dir2"); - await fs.writeFile("/dir1/subdir/file.txt", "subdir"); - - expect(await fs.readFile("/root.txt", "utf8")).toBe("root"); - expect(await fs.readFile("/dir1/file.txt", "utf8")).toBe("dir1"); - expect(await fs.readFile("/dir2/file.txt", "utf8")).toBe("dir2"); - expect(await fs.readFile("/dir1/subdir/file.txt", "utf8")).toBe("subdir"); - - const rootFiles = await fs.readdir("/"); - expect(rootFiles).toContain("root.txt"); - expect(rootFiles).toContain("dir1"); - expect(rootFiles).toContain("dir2"); - }); - - it("should support multiple files with same name in different directories", async () => { - await fs.writeFile("/dir1/config.json", '{"version": 1}'); - await fs.writeFile("/dir2/config.json", '{"version": 2}'); - - expect(await fs.readFile("/dir1/config.json", "utf8")).toBe( - '{"version": 1}' - ); - expect(await fs.readFile("/dir2/config.json", "utf8")).toBe( - '{"version": 2}' - ); - }); - }); - - describe("Standalone Usage", () => { - it("should work with in-memory database when no db provided", async () => { - const standaloneDb = new Database(":memory:"); - await standaloneDb.connect(); - const standaloneFs = await Filesystem.fromDatabase(standaloneDb); - await standaloneFs.writeFile("/test.txt", "standalone content"); - const content = await standaloneFs.readFile("/test.txt", "utf8"); - expect(content).toBe("standalone content"); - await standaloneDb.close(); - }); - - it("should maintain isolation between instances", async () => { - const db1 = new Database(":memory:"); - await db1.connect(); - const fs1 = await Filesystem.fromDatabase(db1); - - const db2 = new Database(":memory:"); - await db2.connect(); - const fs2 = await Filesystem.fromDatabase(db2); - - await fs1.writeFile("/test.txt", "fs1 content"); - await fs2.writeFile("/test.txt", "fs2 content"); - - expect(await fs1.readFile("/test.txt", "utf8")).toBe("fs1 content"); - expect(await fs2.readFile("/test.txt", "utf8")).toBe("fs2 content"); - - await db1.close(); - await db2.close(); - }); - }); - - describe("Persistence", () => { - it("should persist data across Filesystem instances", async () => { - await fs.writeFile("/persist.txt", "persistent content"); - const newFs = await Filesystem.fromDatabase(db); - const content = await newFs.readFile("/persist.txt", "utf8"); - expect(content).toBe("persistent content"); - }); - }); - - // ==================== Chunk Size Boundary Tests ==================== - - describe("Chunk Size Boundary Tests", () => { - // Helper function to get chunk count for an inode - async function getChunkCount(path: string): Promise { - const stmt = db.prepare(` - SELECT COUNT(*) as count FROM fs_data - WHERE ino = (SELECT ino FROM fs_dentry WHERE parent_ino = 1 AND name = ?) - `); - const pathParts = path.split("/").filter((p) => p); - const name = pathParts[pathParts.length - 1]; - - // For simple paths, get the inode from the path - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - - let currentIno = 1; // root - for (const part of pathParts) { - const result = (await lookupStmt.get(currentIno, part)) as - | { ino: number } - | undefined; - if (!result) return 0; - currentIno = result.ino; - } - - const countStmt = db.prepare( - "SELECT COUNT(*) as count FROM fs_data WHERE ino = ?" - ); - const result = (await countStmt.get(currentIno)) as { count: number }; - return result.count; - } - - it("should write file smaller than chunk size", async () => { - // Write a file smaller than chunk_size (100 bytes) - const data = "x".repeat(100); - await fs.writeFile("/small.txt", data); - - // Read it back - const readData = await fs.readFile("/small.txt", "utf8"); - expect(readData.length).toBe(100); - expect(readData).toBe(data); - - // Verify only 1 chunk was created - const chunkCount = await getChunkCount("/small.txt"); - expect(chunkCount).toBe(1); - }); - - it("should write file exactly chunk size", async () => { - const chunkSize = fs.getChunkSize(); - // Write exactly chunk_size bytes - const data = Buffer.alloc(chunkSize); - for (let i = 0; i < chunkSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/exact.txt", data); - - // Read it back - const readData = (await fs.readFile("/exact.txt")) as Buffer; - expect(readData.length).toBe(chunkSize); - - // Verify only 1 chunk was created - const chunkCount = await getChunkCount("/exact.txt"); - expect(chunkCount).toBe(1); - }); - - it("should write file one byte over chunk size", async () => { - const chunkSize = fs.getChunkSize(); - // Write chunk_size + 1 bytes - const data = Buffer.alloc(chunkSize + 1); - for (let i = 0; i <= chunkSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/overflow.txt", data); - - // Read it back - const readData = (await fs.readFile("/overflow.txt")) as Buffer; - expect(readData.length).toBe(chunkSize + 1); - - // Verify 2 chunks were created - const chunkCount = await getChunkCount("/overflow.txt"); - expect(chunkCount).toBe(2); - }); - - it("should write file spanning multiple chunks", async () => { - const chunkSize = fs.getChunkSize(); - // Write ~2.5 chunks worth of data - const dataSize = Math.floor(chunkSize * 2.5); - const data = Buffer.alloc(dataSize); - for (let i = 0; i < dataSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/multi.txt", data); - - // Read it back - const readData = (await fs.readFile("/multi.txt")) as Buffer; - expect(readData.length).toBe(dataSize); - - // Verify 3 chunks were created - const chunkCount = await getChunkCount("/multi.txt"); - expect(chunkCount).toBe(3); - }); - }); - - // ==================== Data Integrity Tests ==================== - - describe("Data Integrity Tests", () => { - async function getChunkCount(path: string): Promise { - const pathParts = path.split("/").filter((p) => p); - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - - let currentIno = 1; - for (const part of pathParts) { - const result = (await lookupStmt.get(currentIno, part)) as - | { ino: number } - | undefined; - if (!result) return 0; - currentIno = result.ino; - } - - const countStmt = db.prepare( - "SELECT COUNT(*) as count FROM fs_data WHERE ino = ?" - ); - const result = (await countStmt.get(currentIno)) as { count: number }; - return result.count; - } - - it("should roundtrip data byte-for-byte", async () => { - const chunkSize = fs.getChunkSize(); - // Create data that spans chunk boundaries with identifiable patterns - const dataSize = chunkSize * 3 + 123; // Odd size spanning 4 chunks - - const data = Buffer.alloc(dataSize); - for (let i = 0; i < dataSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/roundtrip.bin", data); - - const readData = (await fs.readFile("/roundtrip.bin")) as Buffer; - expect(readData.length).toBe(dataSize); - - // Verify chunk count - const chunkCount = await getChunkCount("/roundtrip.bin"); - expect(chunkCount).toBe(4); - }); - - it("should handle binary data with null bytes", async () => { - const chunkSize = fs.getChunkSize(); - // Create data with null bytes at chunk boundaries - const data = Buffer.alloc(chunkSize * 2 + 100); - // Put nulls at the chunk boundary - data[chunkSize - 1] = 0; - data[chunkSize] = 0; - data[chunkSize + 1] = 0; - // Put some non-null bytes around - data[chunkSize - 2] = 0xff; - data[chunkSize + 2] = 0xff; - - await fs.writeFile("/nulls.bin", data); - const readData = (await fs.readFile("/nulls.bin")) as Buffer; - - expect(readData[chunkSize - 2]).toBe(0xff); - expect(readData[chunkSize - 1]).toBe(0); - expect(readData[chunkSize]).toBe(0); - expect(readData[chunkSize + 1]).toBe(0); - expect(readData[chunkSize + 2]).toBe(0xff); - }); - - it("should preserve chunk ordering", async () => { - const chunkSize = fs.getChunkSize(); - // Create sequential bytes spanning multiple chunks - const dataSize = chunkSize * 5; - const data = Buffer.alloc(dataSize); - for (let i = 0; i < dataSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/sequential.bin", data); - - const readData = (await fs.readFile("/sequential.bin")) as Buffer; - - // Verify every byte is in the correct position - for (let i = 0; i < dataSize; i++) { - expect(readData[i]).toBe(i % 256); - } - }); - }); - - // ==================== Edge Case Tests ==================== - - describe("Edge Case Tests", () => { - async function getChunkCount(path: string): Promise { - const pathParts = path.split("/").filter((p) => p); - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - - let currentIno = 1; - for (const part of pathParts) { - const result = (await lookupStmt.get(currentIno, part)) as - | { ino: number } - | undefined; - if (!result) return 0; - currentIno = result.ino; - } - - const countStmt = db.prepare( - "SELECT COUNT(*) as count FROM fs_data WHERE ino = ?" - ); - const result = (await countStmt.get(currentIno)) as { count: number }; - return result.count; - } - - async function getIno(path: string): Promise { - const pathParts = path.split("/").filter((p) => p); - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - - let currentIno = 1; - for (const part of pathParts) { - const result = (await lookupStmt.get(currentIno, part)) as - | { ino: number } - | undefined; - if (!result) return 0; - currentIno = result.ino; - } - return currentIno; - } - - it("should handle empty file with zero chunks", async () => { - // Write empty file - await fs.writeFile("/empty.txt", ""); - - // Read it back - const readData = await fs.readFile("/empty.txt", "utf8"); - expect(readData).toBe(""); - - // Verify 0 chunks were created - const chunkCount = await getChunkCount("/empty.txt"); - expect(chunkCount).toBe(0); - - // Verify size is 0 - const stats = await fs.stat("/empty.txt"); - expect(stats.size).toBe(0); - }); - - it("should overwrite large file with smaller file and clean up chunks", async () => { - const chunkSize = fs.getChunkSize(); - - // Write initial large file (3 chunks) - const initialData = Buffer.alloc(chunkSize * 3); - for (let i = 0; i < chunkSize * 3; i++) { - initialData[i] = i % 256; - } - await fs.writeFile("/overwrite.txt", initialData); - - const ino = await getIno("/overwrite.txt"); - const initialChunkCount = await getChunkCount("/overwrite.txt"); - expect(initialChunkCount).toBe(3); - - // Overwrite with smaller file (1 chunk) - const newData = "x".repeat(100); - await fs.writeFile("/overwrite.txt", newData); - - // Verify old chunks are gone and new data is correct - const readData = await fs.readFile("/overwrite.txt", "utf8"); - expect(readData).toBe(newData); - - const newChunkCount = await getChunkCount("/overwrite.txt"); - expect(newChunkCount).toBe(1); - - // Verify size is updated - const stats = await fs.stat("/overwrite.txt"); - expect(stats.size).toBe(100); - }); - - it("should overwrite small file with larger file", async () => { - const chunkSize = fs.getChunkSize(); - - // Write initial small file (1 chunk) - const initialData = "x".repeat(100); - await fs.writeFile("/grow.txt", initialData); - - expect(await getChunkCount("/grow.txt")).toBe(1); - - // Overwrite with larger file (3 chunks) - const newData = Buffer.alloc(chunkSize * 3); - for (let i = 0; i < chunkSize * 3; i++) { - newData[i] = i % 256; - } - await fs.writeFile("/grow.txt", newData); - - // Verify data is correct (no encoding = Buffer) - const readData = (await fs.readFile("/grow.txt")) as Buffer; - expect(readData.length).toBe(chunkSize * 3); - expect(await getChunkCount("/grow.txt")).toBe(3); - }); - - it("should handle very large file (1MB)", async () => { - // Write 1MB file - const dataSize = 1024 * 1024; - const data = Buffer.alloc(dataSize); - for (let i = 0; i < dataSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/large.bin", data); - - const readData = (await fs.readFile("/large.bin")) as Buffer; - expect(readData.length).toBe(dataSize); - - // Verify correct number of chunks - const chunkSize = fs.getChunkSize(); - const expectedChunks = Math.ceil(dataSize / chunkSize); - const actualChunks = await getChunkCount("/large.bin"); - expect(actualChunks).toBe(expectedChunks); - }); - }); - - // ==================== Configuration Tests ==================== - - describe("Configuration Tests", () => { - it("should have default chunk size of 4096", async () => { - expect(fs.getChunkSize()).toBe(4096); - }); - - it("should verify chunk_size accessor works correctly", async () => { - const chunkSize = fs.getChunkSize(); - expect(chunkSize).toBeGreaterThan(0); - - // Write data and verify chunks match expected based on chunk_size - const data = Buffer.alloc(chunkSize * 2 + 1); - await fs.writeFile("/test.bin", data); - - const pathParts = "/test.bin".split("/").filter((p) => p); - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - let currentIno = 1; - for (const part of pathParts) { - const result = (await lookupStmt.get(currentIno, part)) as - | { ino: number } - | undefined; - if (result) currentIno = result.ino; - } - const countStmt = db.prepare( - "SELECT COUNT(*) as count FROM fs_data WHERE ino = ?" - ); - const result = (await countStmt.get(currentIno)) as { count: number }; - expect(result.count).toBe(3); - }); - - it("should persist chunk_size in fs_config table", async () => { - // Query fs_config table directly - const stmt = db.prepare( - "SELECT value FROM fs_config WHERE key = 'chunk_size'" - ); - const result = (await stmt.get()) as { value: string } | undefined; - - expect(result).toBeDefined(); - expect(result!.value).toBe("4096"); - }); - }); - - // ==================== Schema Tests ==================== - - describe("Schema Tests", () => { - it("should enforce chunk index uniqueness", async () => { - const chunkSize = fs.getChunkSize(); - // Write a file to create chunks - const data = Buffer.alloc(chunkSize * 2); - await fs.writeFile("/unique.txt", data); - - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - const result = (await lookupStmt.get(1, "unique.txt")) as { ino: number }; - const ino = result.ino; - - // Try to insert a duplicate chunk - should fail due to PRIMARY KEY constraint - const insertStmt = db.prepare( - "INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, 0, ?)" - ); - - await expect( - insertStmt.run(ino, Buffer.from([1, 2, 3])) - ).rejects.toThrow(); - }); - - it("should store chunks with correct ordering in database", async () => { - const chunkSize = fs.getChunkSize(); - // Create 5 chunks with identifiable data - const dataSize = chunkSize * 5; - const data = Buffer.alloc(dataSize); - for (let i = 0; i < dataSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/ordered.bin", data); - - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - const result = (await lookupStmt.get(1, "ordered.bin")) as { - ino: number; - }; - const ino = result.ino; - - // Query chunks in order - const queryStmt = db.prepare( - "SELECT chunk_index FROM fs_data WHERE ino = ? ORDER BY chunk_index" - ); - const rows = (await queryStmt.all(ino)) as { chunk_index: number }[]; - - const indices = rows.map((r) => r.chunk_index); - expect(indices).toEqual([0, 1, 2, 3, 4]); - }); - }); - - // ==================== Cleanup Tests ==================== - - describe("Cleanup Tests", () => { - it("should delete all chunks when file is removed", async () => { - const chunkSize = fs.getChunkSize(); - // Create multi-chunk file - const data = Buffer.alloc(chunkSize * 4); - await fs.writeFile("/deleteme.txt", data); - - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - const result = (await lookupStmt.get(1, "deleteme.txt")) as { - ino: number; - }; - const ino = result.ino; - - const countStmt = db.prepare( - "SELECT COUNT(*) as count FROM fs_data WHERE ino = ?" - ); - const beforeResult = (await countStmt.get(ino)) as { count: number }; - expect(beforeResult.count).toBe(4); - - // Delete the file - await fs.unlink('/deleteme.txt'); - - // Verify all chunks are gone - const afterResult = (await countStmt.get(ino)) as { count: number }; - expect(afterResult.count).toBe(0); - }); - - it("should handle multiple files with different sizes correctly", async () => { - const chunkSize = fs.getChunkSize(); - - // Create files of various sizes - const files: [string, number][] = [ - ["/tiny.txt", 10], - ["/small.txt", Math.floor(chunkSize / 2)], - ["/exact.txt", chunkSize], - ["/medium.txt", chunkSize * 2 + 100], - ["/large.txt", chunkSize * 5], - ]; - - for (const [path, size] of files) { - const data = Buffer.alloc(size); - for (let i = 0; i < size; i++) { - data[i] = i % 256; - } - await fs.writeFile(path, data); - } - - // Verify each file has correct data and chunk count - for (const [path, size] of files) { - const readData = (await fs.readFile(path)) as Buffer; - expect(readData.length).toBe(size); - - const expectedChunks = size === 0 ? 0 : Math.ceil(size / chunkSize); - - const name = path.split("/").pop()!; - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - const result = (await lookupStmt.get(1, name)) as { ino: number }; - const countStmt = db.prepare( - "SELECT COUNT(*) as count FROM fs_data WHERE ino = ?" - ); - const countResult = (await countStmt.get(result.ino)) as { - count: number; - }; - - expect(countResult.count).toBe(expectedChunks); - } - }); - }); -}); diff --git a/sdk/typescript/tests/index.test.ts b/sdk/typescript/tests/index.test.ts deleted file mode 100644 index 6ab04726..00000000 --- a/sdk/typescript/tests/index.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { AgentFS } from "../src/index_node.js"; -import { existsSync, rmSync } from "fs"; - -describe("AgentFS Integration Tests", () => { - let agent: AgentFS; - const testId = "test-agent"; - - beforeEach(async () => { - // Initialize AgentFS with a test id - agent = await AgentFS.open({ id: testId }); - }); - - afterEach(async () => { - // Close the agent - await agent.close(); - // Clean up test database files - cleanupAgentFiles(testId); - }); - - describe("Initialization", () => { - it("should successfully initialize with an id", async () => { - expect(agent).toBeDefined(); - expect(agent).toBeInstanceOf(AgentFS); - }); - - it("should initialize with explicit path", async () => { - const pathAgent = await AgentFS.open({ path: ":memory:" }); - expect(pathAgent).toBeDefined(); - expect(pathAgent).toBeInstanceOf(AgentFS); - await pathAgent.close(); - }); - - it("should require at least id or path", async () => { - // @ts-expect-error - Testing runtime validation for JS users - await expect(AgentFS.open({})).rejects.toThrow( - "AgentFS.open() requires at least 'id' or 'path'" - ); - }); - - it("should allow multiple instances with different ids", async () => { - const agent2 = await AgentFS.open({ id: "test-agent-2" }); - - expect(agent).toBeDefined(); - expect(agent2).toBeDefined(); - expect(agent).not.toBe(agent2); - - await agent2.close(); - // Clean up second agent's database - cleanupAgentFiles("test-agent-2"); - }); - }); - - describe("Database Persistence", () => { - it("should persist database file to .agentfs directory", async () => { - // Check that database file exists in .agentfs directory - const dbPath = `.agentfs/${testId}.db`; - expect(existsSync(dbPath)).toBe(true); - }); - - it("should reuse existing database file with same id", async () => { - // Create first instance and write data - const persistenceTestId = "persistence-test"; - const agent1 = await AgentFS.open({ id: persistenceTestId }); - await agent1.kv.set("test", "value1"); - await agent1.close(); - - // Create second instance with same id - should be able to read the data - const agent2 = await AgentFS.open({ id: persistenceTestId }); - const value = await agent2.kv.get("test"); - - expect(agent1).toBeDefined(); - expect(agent2).toBeDefined(); - expect(value).toBe("value1"); - - await agent2.close(); - - // Clean up - cleanupAgentFiles(persistenceTestId); - }); - }); -}); - -/** - * Helper function to clean up agent database files and related SQLite files - * @param id The agent ID to clean up - */ -function cleanupAgentFiles(id: string): void { - const dbPath = `.agentfs/${id}.db`; - try { - // Remove database file and SQLite WAL files - [dbPath, `${dbPath}-shm`, `${dbPath}-wal`].forEach((file) => { - if (existsSync(file)) { - rmSync(file, { force: true }); - } - }); - } catch { - // Ignore cleanup errors - } -} diff --git a/sdk/typescript/tests/kvstore.test.ts b/sdk/typescript/tests/kvstore.test.ts deleted file mode 100644 index 78c85589..00000000 --- a/sdk/typescript/tests/kvstore.test.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { Database } from "@tursodatabase/database"; -import { KvStore } from "../src/kvstore.js"; -import { mkdtempSync, rmSync } from "fs"; -import { tmpdir } from "os"; -import { join } from "path"; - -describe("KvStore Integration Tests", () => { - let db: Database; - let kvStore: KvStore; - let tempDir: string; - let dbPath: string; - - beforeEach(async () => { - // Create temporary directory for test database - tempDir = mkdtempSync(join(tmpdir(), "agentfs-test-")); - dbPath = join(tempDir, "test.db"); - - // Initialize database and KvStore - db = new Database(dbPath); - await db.connect(); - // sync uses CDC so we must ensure that AgentFS components works properly with this extra setup - await db.exec("PRAGMA unstable_capture_data_changes_conn('full')"); - kvStore = await KvStore.fromDatabase(db); - }); - - afterEach(() => { - // Clean up temporary directories - try { - rmSync(tempDir, { recursive: true, force: true }); - } catch { - // Ignore cleanup errors - } - }); - - describe("Basic Operations", () => { - it("should set and get a string value", async () => { - await kvStore.set("test-key", "test-value"); - const value = await kvStore.get("test-key"); - expect(value).toBe("test-value"); - }); - - it("should set and get an object value", async () => { - const testObject = { name: "test", count: 42, nested: { value: true } }; - await kvStore.set("object-key", testObject); - const value = await kvStore.get("object-key"); - expect(value).toEqual(testObject); - }); - - it("should set and get a number value", async () => { - await kvStore.set("number-key", 12345); - const value = await kvStore.get("number-key"); - expect(value).toBe(12345); - }); - - it("should set and get a boolean value", async () => { - await kvStore.set("bool-key", true); - const value = await kvStore.get("bool-key"); - expect(value).toBe(true); - }); - - it("should set and get an array value", async () => { - const testArray = [1, 2, "three", { four: 4 }]; - await kvStore.set("array-key", testArray); - const value = await kvStore.get("array-key"); - expect(value).toEqual(testArray); - }); - it("should set and list values", async () => { - await kvStore.set("g1:k1", 1); - await kvStore.set("g1:k2", 2); - await kvStore.set("g2:k1", 3); - await kvStore.set("g2:k2", 4); - expect(await kvStore.list("g1:")).toEqual([ - { key: "g1:k1", value: 1 }, - { key: "g1:k2", value: 2 }, - ]); - expect(await kvStore.list("g1:k1")).toEqual([{ key: "g1:k1", value: 1 }]); - expect(await kvStore.list("g1:k3")).toEqual([]); - expect(await kvStore.list("g2:")).toEqual([ - { key: "g2:k1", value: 3 }, - { key: "g2:k2", value: 4 }, - ]); - }); - }); - - describe("Update Operations", () => { - it("should update an existing value", async () => { - await kvStore.set("update-key", "initial-value"); - await kvStore.set("update-key", "updated-value"); - const value = await kvStore.get("update-key"); - expect(value).toBe("updated-value"); - }); - - it("should update value type", async () => { - await kvStore.set("type-key", "string-value"); - await kvStore.set("type-key", { object: "value" }); - const value = await kvStore.get("type-key"); - expect(value).toEqual({ object: "value" }); - }); - }); - - describe("Delete Operations", () => { - it("should delete an existing key", async () => { - await kvStore.set("delete-key", "value-to-delete"); - await kvStore.delete("delete-key"); - const value = await kvStore.get("delete-key"); - expect(value).toBeUndefined(); - }); - - it("should handle deleting non-existent key", async () => { - // Should not throw an error when deleting a non-existent key - await kvStore.delete("non-existent-key"); - // If we get here without throwing, the test passes - expect(true).toBe(true); - }); - }); - - describe("Edge Cases", () => { - it("should return undefined for non-existent key", async () => { - const value = await kvStore.get("non-existent-key"); - expect(value).toBeUndefined(); - }); - - it("should handle null values", async () => { - await kvStore.set("null-key", null); - const value = await kvStore.get("null-key"); - expect(value).toBeNull(); - }); - - it("should handle empty string", async () => { - await kvStore.set("empty-key", ""); - const value = await kvStore.get("empty-key"); - expect(value).toBe(""); - }); - - it("should handle zero value", async () => { - await kvStore.set("zero-key", 0); - const value = await kvStore.get("zero-key"); - expect(value).toBe(0); - }); - - it("should handle keys with special characters", async () => { - const specialKey = "key:with/special.chars@123"; - await kvStore.set(specialKey, "value"); - const value = await kvStore.get(specialKey); - expect(value).toBe("value"); - }); - }); - - describe("Concurrent Operations", () => { - it("should handle multiple concurrent sets", async () => { - const promises = Array.from({ length: 10 }, (_, i) => - kvStore.set(`concurrent-key-${i}`, `value-${i}`) - ); - await Promise.all(promises); - - // Verify all values were set - for (let i = 0; i < 10; i++) { - const value = await kvStore.get(`concurrent-key-${i}`); - expect(value).toBe(`value-${i}`); - } - }); - - it("should handle concurrent reads", async () => { - await kvStore.set("read-key", "read-value"); - - const promises = Array.from({ length: 10 }, () => - kvStore.get("read-key") - ); - const values = await Promise.all(promises); - - values.forEach((value) => { - expect(value).toBe("read-value"); - }); - }); - }); - - describe("Large Data", () => { - it("should handle large string values", async () => { - const largeString = "x".repeat(10000); - await kvStore.set("large-string", largeString); - const value = await kvStore.get("large-string"); - expect(value).toBe(largeString); - }); - - it("should handle large object values", async () => { - const largeObject = { - items: Array.from({ length: 1000 }, (_, i) => ({ - id: i, - name: `Item ${i}`, - data: `Data for item ${i}`, - })), - }; - await kvStore.set("large-object", largeObject); - const value = await kvStore.get("large-object"); - expect(value).toEqual(largeObject); - }); - }); - - describe("Persistence", () => { - it("should persist data across KvStore instances", async () => { - await kvStore.set("persist-key", "persist-value"); - - // Create new KvStore instance with same database - const newKvStore = await KvStore.fromDatabase(db); - const value = await newKvStore.get("persist-key"); - expect(value).toBe("persist-value"); - }); - }); -}); diff --git a/sdk/typescript/tests/serverless.test.ts b/sdk/typescript/tests/serverless.test.ts deleted file mode 100644 index c25a08d1..00000000 --- a/sdk/typescript/tests/serverless.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, it, expect, beforeAll, afterAll } from "vitest"; -import { connect } from "@tursodatabase/serverless"; -import { createServerlessAdapter } from "../src/integrations/serverless/index.js"; -import { AgentFS } from "../src/index_node.js"; - -const SQLD_URL = process.env.SQLD_URL || "http://localhost:8080"; - -// Check if sqld is reachable before running tests -async function isSqldAvailable(): Promise { - try { - const resp = await fetch(`${SQLD_URL}/version`); - return resp.ok; - } catch { - return false; - } -} - -const sqldAvailable = await isSqldAvailable(); - -describe.skipIf(!sqldAvailable)("Serverless Integration", () => { - let agent: Awaited>; - - beforeAll(async () => { - const conn = connect({ url: SQLD_URL }); - const db = createServerlessAdapter(conn); - agent = await AgentFS.openWith(db); - }); - - afterAll(async () => { - try { - await agent.fs.rm("/test-serverless", { recursive: true, force: true }); - } catch {} - await agent.close(); - }); - - it("should write and read a file", async () => { - await agent.fs.writeFile("/test-serverless/hello.txt", "Hello from serverless!"); - const content = await agent.fs.readFile("/test-serverless/hello.txt", "utf8"); - expect(content).toBe("Hello from serverless!"); - }); - - it("should list directory contents", async () => { - await agent.fs.writeFile("/test-serverless/a.txt", "a"); - await agent.fs.writeFile("/test-serverless/b.txt", "b"); - const entries = await agent.fs.readdir("/test-serverless"); - expect(entries).toContain("a.txt"); - expect(entries).toContain("b.txt"); - }); - - it("should stat a file", async () => { - await agent.fs.writeFile("/test-serverless/stat-test.txt", "test content"); - const stats = await agent.fs.stat("/test-serverless/stat-test.txt"); - expect(stats.isFile()).toBe(true); - expect(stats.isDirectory()).toBe(false); - expect(stats.size).toBe(12); - }); - - it("should use kv store", async () => { - await agent.kv.set("test-key", { value: 42 }); - const result = await agent.kv.get("test-key"); - expect(result).toEqual({ value: 42 }); - }); - - it("should delete a file", async () => { - await agent.fs.writeFile("/test-serverless/delete-me.txt", "bye"); - await agent.fs.unlink("/test-serverless/delete-me.txt"); - await expect(agent.fs.stat("/test-serverless/delete-me.txt")).rejects.toThrow(); - }); -}); diff --git a/sdk/typescript/tests/toolcalls.test.ts b/sdk/typescript/tests/toolcalls.test.ts deleted file mode 100644 index 8696d2ae..00000000 --- a/sdk/typescript/tests/toolcalls.test.ts +++ /dev/null @@ -1,323 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { Database } from "@tursodatabase/database"; -import { ToolCalls } from "../src/toolcalls.js"; -import { mkdtempSync, rmSync } from "fs"; -import { tmpdir } from "os"; -import { join } from "path"; - -describe("ToolCalls Integration Tests", () => { - let db: Database; - let toolCalls: ToolCalls; - let tempDir: string; - let dbPath: string; - - beforeEach(async () => { - // Create temporary directory for test database - tempDir = mkdtempSync(join(tmpdir(), "agent-datakit-test-")); - dbPath = join(tempDir, "test.db"); - - // Initialize database and ToolCalls - db = new Database(dbPath); - await db.connect(); - // sync uses CDC so we must ensure that AgentFS components works properly with this extra setup - await db.exec("PRAGMA unstable_capture_data_changes_conn('full')"); - toolCalls = await ToolCalls.fromDatabase(db); - }); - - afterEach(() => { - // Clean up temporary directories - try { - rmSync(tempDir, { recursive: true, force: true }); - } catch { - // Ignore cleanup errors - } - }); - - describe("Basic Operations", () => { - it("should start a tool call and return an ID", async () => { - const id = await toolCalls.start("test_tool", { arg1: "value1" }); - expect(id).toBeGreaterThan(0); - }); - - it("should start a tool call without parameters", async () => { - const id = await toolCalls.start("simple_tool"); - expect(id).toBeGreaterThan(0); - - const toolCall = await toolCalls.get(id); - expect(toolCall).toBeDefined(); - expect(toolCall?.name).toBe("simple_tool"); - expect(toolCall?.parameters).toBeUndefined(); - expect(toolCall?.status).toBe("pending"); - }); - - it("should mark a tool call as successful", async () => { - const id = await toolCalls.start("test_tool", { input: "test" }); - await toolCalls.success(id, { output: "result" }); - - const toolCall = await toolCalls.get(id); - expect(toolCall).toBeDefined(); - expect(toolCall?.status).toBe("success"); - expect(toolCall?.result).toEqual({ output: "result" }); - expect(toolCall?.completed_at).toBeGreaterThan(0); - expect(toolCall?.duration_ms).toBeGreaterThanOrEqual(0); - }); - - it("should mark a tool call as successful without result", async () => { - const id = await toolCalls.start("test_tool", { input: "test" }); - await toolCalls.success(id); - - const toolCall = await toolCalls.get(id); - expect(toolCall).toBeDefined(); - expect(toolCall?.status).toBe("success"); - expect(toolCall?.result).toBeUndefined(); - }); - - it("should mark a tool call as failed", async () => { - const id = await toolCalls.start("test_tool", { input: "test" }); - await toolCalls.error(id, "Something went wrong"); - - const toolCall = await toolCalls.get(id); - expect(toolCall).toBeDefined(); - expect(toolCall?.status).toBe("error"); - expect(toolCall?.error).toBe("Something went wrong"); - expect(toolCall?.completed_at).toBeGreaterThan(0); - expect(toolCall?.duration_ms).toBeGreaterThanOrEqual(0); - }); - - it("should get a tool call by ID", async () => { - const id = await toolCalls.start("test_tool", { arg: "value" }); - const toolCall = await toolCalls.get(id); - - expect(toolCall).toBeDefined(); - expect(toolCall?.id).toBe(id); - expect(toolCall?.name).toBe("test_tool"); - expect(toolCall?.parameters).toEqual({ arg: "value" }); - expect(toolCall?.status).toBe("pending"); - expect(toolCall?.started_at).toBeGreaterThan(0); - }); - - it("should return undefined for non-existent ID", async () => { - const toolCall = await toolCalls.get(99999); - expect(toolCall).toBeUndefined(); - }); - }); - - describe("Query Operations", () => { - it("should get tool calls by name", async () => { - await toolCalls.start("tool_a", { test: 1 }); - await toolCalls.start("tool_b", { test: 2 }); - await toolCalls.start("tool_a", { test: 3 }); - - const results = await toolCalls.getByName("tool_a"); - expect(results).toHaveLength(2); - expect(results.every((tc) => tc.name === "tool_a")).toBe(true); - }); - - it("should limit results when querying by name", async () => { - await toolCalls.start("tool_a", { test: 1 }); - await toolCalls.start("tool_a", { test: 2 }); - await toolCalls.start("tool_a", { test: 3 }); - - const results = await toolCalls.getByName("tool_a", 2); - expect(results).toHaveLength(2); - }); - - it("should get recent tool calls", async () => { - await toolCalls.start("tool_old"); - // Wait a full second to ensure different timestamps - await new Promise((resolve) => setTimeout(resolve, 1100)); - const midpoint = Math.floor(Date.now() / 1000); - // Wait a bit more to ensure next call is definitely after midpoint - await new Promise((resolve) => setTimeout(resolve, 1100)); - await toolCalls.start("tool_new"); - - const results = await toolCalls.getRecent(midpoint); - expect(results.length).toBeGreaterThanOrEqual(1); - expect(results.every((tc) => tc.started_at >= midpoint)).toBe(true); - }); - - it("should limit recent tool calls", async () => { - const now = Math.floor(Date.now() / 1000); - - await toolCalls.start("tool_1"); - await toolCalls.start("tool_2"); - await toolCalls.start("tool_3"); - - const results = await toolCalls.getRecent(now - 10, 2); - expect(results.length).toBeLessThanOrEqual(2); - }); - - it("should return empty array when no matching tool calls by name", async () => { - const results = await toolCalls.getByName("non_existent_tool"); - expect(results).toEqual([]); - }); - }); - - describe("Statistics", () => { - it("should calculate tool call statistics", async () => { - // Create some tool calls - const id1 = await toolCalls.start("tool_a", { test: 1 }); - await toolCalls.success(id1, { result: "ok" }); - - const id2 = await toolCalls.start("tool_a", { test: 2 }); - await toolCalls.error(id2, "failed"); - - const id3 = await toolCalls.start("tool_a", { test: 3 }); - await toolCalls.success(id3, { result: "ok" }); - - const id4 = await toolCalls.start("tool_b", { test: 4 }); - await toolCalls.success(id4, { result: "ok" }); - - const stats = await toolCalls.getStats(); - - expect(stats).toHaveLength(2); - - const toolAStats = stats.find((s) => s.name === "tool_a"); - expect(toolAStats).toBeDefined(); - expect(toolAStats?.total_calls).toBe(3); - expect(toolAStats?.successful).toBe(2); - expect(toolAStats?.failed).toBe(1); - expect(toolAStats?.avg_duration_ms).toBeGreaterThanOrEqual(0); - - const toolBStats = stats.find((s) => s.name === "tool_b"); - expect(toolBStats).toBeDefined(); - expect(toolBStats?.total_calls).toBe(1); - expect(toolBStats?.successful).toBe(1); - expect(toolBStats?.failed).toBe(0); - }); - - it("should exclude pending calls from statistics", async () => { - const id1 = await toolCalls.start("tool_a", { test: 1 }); - await toolCalls.success(id1, { result: "ok" }); - - // This one stays pending - await toolCalls.start("tool_a", { test: 2 }); - - const stats = await toolCalls.getStats(); - const toolAStats = stats.find((s) => s.name === "tool_a"); - - expect(toolAStats).toBeDefined(); - expect(toolAStats?.total_calls).toBe(1); // Only completed calls - }); - - it("should return empty array when no completed calls", async () => { - await toolCalls.start("tool_a", { test: 1 }); - const stats = await toolCalls.getStats(); - expect(stats).toEqual([]); - }); - }); - - describe("Complex Parameters and Results", () => { - it("should handle complex nested parameters", async () => { - const complexParams = { - user: { id: 123, name: "Test User" }, - options: { timeout: 5000, retry: true }, - data: [1, 2, 3, 4, 5], - }; - - const id = await toolCalls.start("complex_tool", complexParams); - const toolCall = await toolCalls.get(id); - - expect(toolCall?.parameters).toEqual(complexParams); - }); - - it("should handle complex nested results", async () => { - const complexResult = { - data: { - items: [ - { id: 1, value: "a" }, - { id: 2, value: "b" }, - ], - }, - metadata: { count: 2, hasMore: false }, - }; - - const id = await toolCalls.start("complex_tool"); - await toolCalls.success(id, complexResult); - const toolCall = await toolCalls.get(id); - - expect(toolCall?.result).toEqual(complexResult); - }); - - it("should handle large parameters", async () => { - const largeParams = { - items: Array.from({ length: 100 }, (_, i) => ({ - id: i, - data: `Data for item ${i}`, - })), - }; - - const id = await toolCalls.start("large_tool", largeParams); - const toolCall = await toolCalls.get(id); - - expect(toolCall?.parameters).toEqual(largeParams); - }); - }); - - describe("Concurrent Operations", () => { - it("should handle multiple concurrent tool calls", async () => { - const promises = Array.from({ length: 10 }, (_, i) => - toolCalls.start(`tool_${i}`, { index: i }) - ); - const ids = await Promise.all(promises); - - expect(ids).toHaveLength(10); - expect(new Set(ids).size).toBe(10); // All IDs should be unique - }); - - it("should handle concurrent completions", async () => { - const ids = await Promise.all([ - toolCalls.start("tool_1"), - toolCalls.start("tool_2"), - toolCalls.start("tool_3"), - ]); - - await Promise.all([ - toolCalls.success(ids[0]), - toolCalls.error(ids[1], "error"), - toolCalls.success(ids[2]), - ]); - - const results = await Promise.all(ids.map((id) => toolCalls.get(id))); - - expect(results[0]?.status).toBe("success"); - expect(results[1]?.status).toBe("error"); - expect(results[2]?.status).toBe("success"); - }); - }); - - describe("Persistence", () => { - it("should persist tool calls across instances", async () => { - const id = await toolCalls.start("persist_tool", { test: "value" }); - await toolCalls.success(id, { result: "ok" }); - - // Create new ToolCalls instance with same database - const newToolCalls = await ToolCalls.fromDatabase(db); - const toolCall = await newToolCalls.get(id); - - expect(toolCall).toBeDefined(); - expect(toolCall?.name).toBe("persist_tool"); - expect(toolCall?.status).toBe("success"); - }); - }); - - describe("Ordering", () => { - it("should return tool calls ordered by started_at desc", async () => { - await toolCalls.start("tool_first"); - await new Promise((resolve) => setTimeout(resolve, 100)); - await toolCalls.start("tool_second"); - await new Promise((resolve) => setTimeout(resolve, 100)); - await toolCalls.start("tool_third"); - - const recent = await toolCalls.getRecent(0); - - expect(recent.length).toBeGreaterThanOrEqual(3); - // Most recent first - for (let i = 0; i < recent.length - 1; i++) { - expect(recent[i].started_at).toBeGreaterThanOrEqual( - recent[i + 1].started_at - ); - } - }); - }); -}); diff --git a/sdk/typescript/tests/tsconfig.json b/sdk/typescript/tests/tsconfig.json deleted file mode 100644 index 9cefe1b2..00000000 --- a/sdk/typescript/tests/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "noEmit": true, - "types": ["node", "vitest/globals"], - "rootDir": ".." - }, - "include": ["**/*.ts"] -} - - diff --git a/sdk/typescript/tests_browser/filesystem.test.ts b/sdk/typescript/tests_browser/filesystem.test.ts deleted file mode 100644 index d60e3a69..00000000 --- a/sdk/typescript/tests_browser/filesystem.test.ts +++ /dev/null @@ -1,784 +0,0 @@ -import { describe, it, expect, beforeEach } from "vitest"; -import { Database } from "@tursodatabase/database-wasm/vite"; -import { AgentFS as Filesystem } from "../src/filesystem/agentfs.js"; - -import { Buffer } from "buffer"; -globalThis.Buffer = Buffer; - -describe("Filesystem Integration Tests", () => { - let db: Database; - let fs: Filesystem; - - beforeEach(async () => { - // Initialize database and Filesystem - db = new Database(":memory:"); - await db.connect(); - // sync uses CDC so we must ensure that AgentFS components works properly with this extra setup - await db.exec("PRAGMA unstable_capture_data_changes_conn('full')"); - fs = await Filesystem.fromDatabase(db); - }); - - describe("File Write Operations", () => { - it("should write and read a simple text file", async () => { - await fs.writeFile("/test.txt", "Hello, World!"); - const content = await fs.readFile("/test.txt", "utf8"); - expect(content).toBe("Hello, World!"); - }); - - it("should write and read files in subdirectories", async () => { - await fs.writeFile("/dir/subdir/file.txt", "nested content"); - const content = await fs.readFile("/dir/subdir/file.txt", "utf8"); - expect(content).toBe("nested content"); - }); - - it("should overwrite existing file", async () => { - await fs.writeFile("/overwrite.txt", "original content"); - await fs.writeFile("/overwrite.txt", "new content"); - const content = await fs.readFile("/overwrite.txt", "utf8"); - expect(content).toBe("new content"); - }); - - it("should handle empty file content", async () => { - await fs.writeFile("/empty.txt", ""); - const content = await fs.readFile("/empty.txt", "utf8"); - expect(content).toBe(""); - }); - - it("should handle large file content", async () => { - const largeContent = "x".repeat(100000); - await fs.writeFile("/large.txt", largeContent); - const content = await fs.readFile("/large.txt", "utf8"); - expect(content).toBe(largeContent); - }); - - it("should handle files with special characters in content", async () => { - const specialContent = "Special chars: \n\t\r\"'\\"; - await fs.writeFile("/special.txt", specialContent); - const content = await fs.readFile("/special.txt", "utf8"); - expect(content).toBe(specialContent); - }); - }); - - describe("File Read Operations", () => { - it("should throw error when reading non-existent file", async () => { - await expect(fs.readFile("/non-existent.txt")).rejects.toThrow(); - }); - - it("should read multiple different files", async () => { - await fs.writeFile("/file1.txt", "content 1"); - await fs.writeFile("/file2.txt", "content 2"); - await fs.writeFile("/file3.txt", "content 3"); - - expect(await fs.readFile("/file1.txt", "utf8")).toBe("content 1"); - expect(await fs.readFile("/file2.txt", "utf8")).toBe("content 2"); - expect(await fs.readFile("/file3.txt", "utf8")).toBe("content 3"); - }); - }); - - describe("Directory Operations", () => { - it("should list files in root directory", async () => { - await fs.writeFile("/file1.txt", "content 1"); - await fs.writeFile("/file2.txt", "content 2"); - await fs.writeFile("/file3.txt", "content 3"); - - const files = await fs.readdir("/"); - expect(files).toContain("file1.txt"); - expect(files).toContain("file2.txt"); - expect(files).toContain("file3.txt"); - expect(files).toHaveLength(3); - }); - - it("should list files in subdirectory", async () => { - await fs.writeFile("/dir/file1.txt", "content 1"); - await fs.writeFile("/dir/file2.txt", "content 2"); - await fs.writeFile("/other/file3.txt", "content 3"); - - const files = await fs.readdir("/dir"); - expect(files).toContain("file1.txt"); - expect(files).toContain("file2.txt"); - expect(files).not.toContain("file3.txt"); - expect(files).toHaveLength(2); - }); - - it("should return empty array for empty directory", async () => { - await fs.writeFile("/dir/file.txt", "content"); - // /dir exists but is not empty, root exists and should be empty except for 'dir' - const files = await fs.readdir("/"); - expect(files).toContain("dir"); - }); - - it("should distinguish between files in different directories", async () => { - await fs.writeFile("/dir1/file.txt", "content 1"); - await fs.writeFile("/dir2/file.txt", "content 2"); - - const files1 = await fs.readdir("/dir1"); - const files2 = await fs.readdir("/dir2"); - - expect(files1).toContain("file.txt"); - expect(files2).toContain("file.txt"); - expect(files1).toHaveLength(1); - expect(files2).toHaveLength(1); - }); - - it("should list subdirectories within a directory", async () => { - await fs.writeFile("/parent/child1/file.txt", "content"); - await fs.writeFile("/parent/child2/file.txt", "content"); - await fs.writeFile("/parent/file.txt", "content"); - - const entries = await fs.readdir("/parent"); - expect(entries).toContain("file.txt"); - expect(entries).toContain("child1"); - expect(entries).toContain("child2"); - }); - - it("should handle nested directory structures", async () => { - await fs.writeFile("/a/b/c/d/file.txt", "deep content"); - const files = await fs.readdir("/a/b/c/d"); - expect(files).toContain("file.txt"); - }); - }); - - describe("File Delete Operations", () => { - it("should delete an existing file", async () => { - await fs.writeFile("/delete-me.txt", "content"); - await fs.deleteFile("/delete-me.txt"); - await expect(fs.readFile("/delete-me.txt")).rejects.toThrow(); - }); - - it("should handle deleting non-existent file", async () => { - await expect(fs.deleteFile("/non-existent.txt")).rejects.toThrow( - "ENOENT" - ); - }); - - it("should delete file and update directory listing", async () => { - await fs.writeFile("/dir/file1.txt", "content 1"); - await fs.writeFile("/dir/file2.txt", "content 2"); - - await fs.deleteFile("/dir/file1.txt"); - - const files = await fs.readdir("/dir"); - expect(files).not.toContain("file1.txt"); - expect(files).toContain("file2.txt"); - expect(files).toHaveLength(1); - }); - - it("should allow recreating deleted file", async () => { - await fs.writeFile("/recreate.txt", "original"); - await fs.deleteFile("/recreate.txt"); - await fs.writeFile("/recreate.txt", "new content"); - const content = await fs.readFile("/recreate.txt", "utf8"); - expect(content).toBe("new content"); - }); - }); - - describe("Path Handling", () => { - it("should handle paths with trailing slashes", async () => { - await fs.writeFile("/dir/file.txt", "content"); - const files1 = await fs.readdir("/dir"); - const files2 = await fs.readdir("/dir/"); - expect(files1).toEqual(files2); - }); - - it("should handle paths with special characters", async () => { - const specialPath = "/dir-with-dash/file_with_underscore.txt"; - await fs.writeFile(specialPath, "content"); - const content = await fs.readFile(specialPath, "utf8"); - expect(content).toBe("content"); - }); - }); - - describe("Concurrent Operations", () => { - it("should handle concurrent writes to different files", async () => { - const operations = Array.from({ length: 10 }, (_, i) => - fs.writeFile(`/concurrent-${i}.txt`, `content ${i}`) - ); - await Promise.all(operations); - - // Verify all files were created - for (let i = 0; i < 10; i++) { - const content = await fs.readFile(`/concurrent-${i}.txt`, "utf8"); - expect(content).toBe(`content ${i}`); - } - }); - - it("should handle concurrent reads", async () => { - await fs.writeFile("/concurrent-read.txt", "shared content"); - - const results = await Promise.all( - Array.from({ length: 10 }, () => - fs.readFile("/concurrent-read.txt", "utf8") - ) - ); - - results.forEach((content) => { - expect(content).toBe("shared content"); - }); - }); - }); - - describe("File System Integrity", () => { - it("should maintain file hierarchy integrity", async () => { - await fs.writeFile("/root.txt", "root"); - await fs.writeFile("/dir1/file.txt", "dir1"); - await fs.writeFile("/dir2/file.txt", "dir2"); - await fs.writeFile("/dir1/subdir/file.txt", "subdir"); - - expect(await fs.readFile("/root.txt", "utf8")).toBe("root"); - expect(await fs.readFile("/dir1/file.txt", "utf8")).toBe("dir1"); - expect(await fs.readFile("/dir2/file.txt", "utf8")).toBe("dir2"); - expect(await fs.readFile("/dir1/subdir/file.txt", "utf8")).toBe("subdir"); - - const rootFiles = await fs.readdir("/"); - expect(rootFiles).toContain("root.txt"); - expect(rootFiles).toContain("dir1"); - expect(rootFiles).toContain("dir2"); - }); - - it("should support multiple files with same name in different directories", async () => { - await fs.writeFile("/dir1/config.json", '{"version": 1}'); - await fs.writeFile("/dir2/config.json", '{"version": 2}'); - - expect(await fs.readFile("/dir1/config.json", "utf8")).toBe( - '{"version": 1}' - ); - expect(await fs.readFile("/dir2/config.json", "utf8")).toBe( - '{"version": 2}' - ); - }); - }); - - describe("Standalone Usage", () => { - it("should work with in-memory database when no db provided", async () => { - const standaloneDb = new Database(":memory:"); - await standaloneDb.connect(); - const standaloneFs = await Filesystem.fromDatabase(standaloneDb); - await standaloneFs.writeFile("/test.txt", "standalone content"); - const content = await standaloneFs.readFile("/test.txt", "utf8"); - expect(content).toBe("standalone content"); - await standaloneDb.close(); - }); - - it("should maintain isolation between instances", async () => { - const db1 = new Database(":memory:"); - await db1.connect(); - const fs1 = await Filesystem.fromDatabase(db1); - - const db2 = new Database(":memory:"); - await db2.connect(); - const fs2 = await Filesystem.fromDatabase(db2); - - await fs1.writeFile("/test.txt", "fs1 content"); - await fs2.writeFile("/test.txt", "fs2 content"); - - expect(await fs1.readFile("/test.txt", "utf8")).toBe("fs1 content"); - expect(await fs2.readFile("/test.txt", "utf8")).toBe("fs2 content"); - - await db1.close(); - await db2.close(); - }); - }); - - describe("Persistence", () => { - it("should persist data across Filesystem instances", async () => { - await fs.writeFile("/persist.txt", "persistent content"); - const newFs = await Filesystem.fromDatabase(db); - const content = await newFs.readFile("/persist.txt", "utf8"); - expect(content).toBe("persistent content"); - }); - }); - - // ==================== Chunk Size Boundary Tests ==================== - - describe("Chunk Size Boundary Tests", () => { - // Helper function to get chunk count for an inode - async function getChunkCount(path: string): Promise { - const stmt = db.prepare(` - SELECT COUNT(*) as count FROM fs_data - WHERE ino = (SELECT ino FROM fs_dentry WHERE parent_ino = 1 AND name = ?) - `); - const pathParts = path.split("/").filter((p) => p); - const name = pathParts[pathParts.length - 1]; - - // For simple paths, get the inode from the path - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - - let currentIno = 1; // root - for (const part of pathParts) { - const result = (await lookupStmt.get(currentIno, part)) as - | { ino: number } - | undefined; - if (!result) return 0; - currentIno = result.ino; - } - - const countStmt = db.prepare( - "SELECT COUNT(*) as count FROM fs_data WHERE ino = ?" - ); - const result = (await countStmt.get(currentIno)) as { count: number }; - return result.count; - } - - it("should write file smaller than chunk size", async () => { - // Write a file smaller than chunk_size (100 bytes) - const data = "x".repeat(100); - await fs.writeFile("/small.txt", data); - - // Read it back - const readData = await fs.readFile("/small.txt", "utf8"); - expect(readData.length).toBe(100); - expect(readData).toBe(data); - - // Verify only 1 chunk was created - const chunkCount = await getChunkCount("/small.txt"); - expect(chunkCount).toBe(1); - }); - - it("should write file exactly chunk size", async () => { - const chunkSize = fs.getChunkSize(); - // Write exactly chunk_size bytes - const data = Buffer.alloc(chunkSize); - for (let i = 0; i < chunkSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/exact.txt", data); - - // Read it back - const readData = (await fs.readFile("/exact.txt")) as Buffer; - expect(readData.length).toBe(chunkSize); - - // Verify only 1 chunk was created - const chunkCount = await getChunkCount("/exact.txt"); - expect(chunkCount).toBe(1); - }); - - it("should write file one byte over chunk size", async () => { - const chunkSize = fs.getChunkSize(); - // Write chunk_size + 1 bytes - const data = Buffer.alloc(chunkSize + 1); - for (let i = 0; i <= chunkSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/overflow.txt", data); - - // Read it back - const readData = (await fs.readFile("/overflow.txt")) as Buffer; - expect(readData.length).toBe(chunkSize + 1); - - // Verify 2 chunks were created - const chunkCount = await getChunkCount("/overflow.txt"); - expect(chunkCount).toBe(2); - }); - - it("should write file spanning multiple chunks", async () => { - const chunkSize = fs.getChunkSize(); - // Write ~2.5 chunks worth of data - const dataSize = Math.floor(chunkSize * 2.5); - const data = Buffer.alloc(dataSize); - for (let i = 0; i < dataSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/multi.txt", data); - - // Read it back - const readData = (await fs.readFile("/multi.txt")) as Buffer; - expect(readData.length).toBe(dataSize); - - // Verify 3 chunks were created - const chunkCount = await getChunkCount("/multi.txt"); - expect(chunkCount).toBe(3); - }); - }); - - // ==================== Data Integrity Tests ==================== - - describe("Data Integrity Tests", () => { - async function getChunkCount(path: string): Promise { - const pathParts = path.split("/").filter((p) => p); - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - - let currentIno = 1; - for (const part of pathParts) { - const result = (await lookupStmt.get(currentIno, part)) as - | { ino: number } - | undefined; - if (!result) return 0; - currentIno = result.ino; - } - - const countStmt = db.prepare( - "SELECT COUNT(*) as count FROM fs_data WHERE ino = ?" - ); - const result = (await countStmt.get(currentIno)) as { count: number }; - return result.count; - } - - it("should roundtrip data byte-for-byte", async () => { - const chunkSize = fs.getChunkSize(); - // Create data that spans chunk boundaries with identifiable patterns - const dataSize = chunkSize * 3 + 123; // Odd size spanning 4 chunks - - const data = Buffer.alloc(dataSize); - for (let i = 0; i < dataSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/roundtrip.bin", data); - - const readData = (await fs.readFile("/roundtrip.bin")) as Buffer; - expect(readData.length).toBe(dataSize); - - // Verify chunk count - const chunkCount = await getChunkCount("/roundtrip.bin"); - expect(chunkCount).toBe(4); - }); - - it("should handle binary data with null bytes", async () => { - const chunkSize = fs.getChunkSize(); - // Create data with null bytes at chunk boundaries - const data = Buffer.alloc(chunkSize * 2 + 100); - // Put nulls at the chunk boundary - data[chunkSize - 1] = 0; - data[chunkSize] = 0; - data[chunkSize + 1] = 0; - // Put some non-null bytes around - data[chunkSize - 2] = 0xff; - data[chunkSize + 2] = 0xff; - - await fs.writeFile("/nulls.bin", data); - const readData = (await fs.readFile("/nulls.bin")) as Buffer; - - expect(readData[chunkSize - 2]).toBe(0xff); - expect(readData[chunkSize - 1]).toBe(0); - expect(readData[chunkSize]).toBe(0); - expect(readData[chunkSize + 1]).toBe(0); - expect(readData[chunkSize + 2]).toBe(0xff); - }); - - it("should preserve chunk ordering", async () => { - const chunkSize = fs.getChunkSize(); - // Create sequential bytes spanning multiple chunks - const dataSize = chunkSize * 5; - const data = Buffer.alloc(dataSize); - for (let i = 0; i < dataSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/sequential.bin", data); - - const readData = (await fs.readFile("/sequential.bin")) as Buffer; - - // Verify every byte is in the correct position - for (let i = 0; i < dataSize; i++) { - expect(readData[i]).toBe(i % 256); - } - }); - }); - - // ==================== Edge Case Tests ==================== - - describe("Edge Case Tests", () => { - async function getChunkCount(path: string): Promise { - const pathParts = path.split("/").filter((p) => p); - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - - let currentIno = 1; - for (const part of pathParts) { - const result = (await lookupStmt.get(currentIno, part)) as - | { ino: number } - | undefined; - if (!result) return 0; - currentIno = result.ino; - } - - const countStmt = db.prepare( - "SELECT COUNT(*) as count FROM fs_data WHERE ino = ?" - ); - const result = (await countStmt.get(currentIno)) as { count: number }; - return result.count; - } - - async function getIno(path: string): Promise { - const pathParts = path.split("/").filter((p) => p); - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - - let currentIno = 1; - for (const part of pathParts) { - const result = (await lookupStmt.get(currentIno, part)) as - | { ino: number } - | undefined; - if (!result) return 0; - currentIno = result.ino; - } - return currentIno; - } - - it("should handle empty file with zero chunks", async () => { - // Write empty file - await fs.writeFile("/empty.txt", ""); - - // Read it back - const readData = await fs.readFile("/empty.txt", "utf8"); - expect(readData).toBe(""); - - // Verify 0 chunks were created - const chunkCount = await getChunkCount("/empty.txt"); - expect(chunkCount).toBe(0); - - // Verify size is 0 - const stats = await fs.stat("/empty.txt"); - expect(stats.size).toBe(0); - }); - - it("should overwrite large file with smaller file and clean up chunks", async () => { - const chunkSize = fs.getChunkSize(); - - // Write initial large file (3 chunks) - const initialData = Buffer.alloc(chunkSize * 3); - for (let i = 0; i < chunkSize * 3; i++) { - initialData[i] = i % 256; - } - await fs.writeFile("/overwrite.txt", initialData); - - const ino = await getIno("/overwrite.txt"); - const initialChunkCount = await getChunkCount("/overwrite.txt"); - expect(initialChunkCount).toBe(3); - - // Overwrite with smaller file (1 chunk) - const newData = "x".repeat(100); - await fs.writeFile("/overwrite.txt", newData); - - // Verify old chunks are gone and new data is correct - const readData = await fs.readFile("/overwrite.txt", "utf8"); - expect(readData).toBe(newData); - - const newChunkCount = await getChunkCount("/overwrite.txt"); - expect(newChunkCount).toBe(1); - - // Verify size is updated - const stats = await fs.stat("/overwrite.txt"); - expect(stats.size).toBe(100); - }); - - it("should overwrite small file with larger file", async () => { - const chunkSize = fs.getChunkSize(); - - // Write initial small file (1 chunk) - const initialData = "x".repeat(100); - await fs.writeFile("/grow.txt", initialData); - - expect(await getChunkCount("/grow.txt")).toBe(1); - - // Overwrite with larger file (3 chunks) - const newData = Buffer.alloc(chunkSize * 3); - for (let i = 0; i < chunkSize * 3; i++) { - newData[i] = i % 256; - } - await fs.writeFile("/grow.txt", newData); - - // Verify data is correct (no encoding = Buffer) - const readData = (await fs.readFile("/grow.txt")) as Buffer; - expect(readData.length).toBe(chunkSize * 3); - expect(await getChunkCount("/grow.txt")).toBe(3); - }); - - it("should handle very large file (1MB)", async () => { - // Write 1MB file - const dataSize = 1024 * 1024; - const data = Buffer.alloc(dataSize); - for (let i = 0; i < dataSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/large.bin", data); - - const readData = (await fs.readFile("/large.bin")) as Buffer; - expect(readData.length).toBe(dataSize); - - // Verify correct number of chunks - const chunkSize = fs.getChunkSize(); - const expectedChunks = Math.ceil(dataSize / chunkSize); - const actualChunks = await getChunkCount("/large.bin"); - expect(actualChunks).toBe(expectedChunks); - }); - }); - - // ==================== Configuration Tests ==================== - - describe("Configuration Tests", () => { - it("should have default chunk size of 4096", async () => { - expect(fs.getChunkSize()).toBe(4096); - }); - - it("should verify chunk_size accessor works correctly", async () => { - const chunkSize = fs.getChunkSize(); - expect(chunkSize).toBeGreaterThan(0); - - // Write data and verify chunks match expected based on chunk_size - const data = Buffer.alloc(chunkSize * 2 + 1); - await fs.writeFile("/test.bin", data); - - const pathParts = "/test.bin".split("/").filter((p) => p); - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - let currentIno = 1; - for (const part of pathParts) { - const result = (await lookupStmt.get(currentIno, part)) as - | { ino: number } - | undefined; - if (result) currentIno = result.ino; - } - const countStmt = db.prepare( - "SELECT COUNT(*) as count FROM fs_data WHERE ino = ?" - ); - const result = (await countStmt.get(currentIno)) as { count: number }; - expect(result.count).toBe(3); - }); - - it("should persist chunk_size in fs_config table", async () => { - // Query fs_config table directly - const stmt = db.prepare( - "SELECT value FROM fs_config WHERE key = 'chunk_size'" - ); - const result = (await stmt.get()) as { value: string } | undefined; - - expect(result).toBeDefined(); - expect(result!.value).toBe("4096"); - }); - }); - - // ==================== Schema Tests ==================== - - describe("Schema Tests", () => { - it("should enforce chunk index uniqueness", async () => { - const chunkSize = fs.getChunkSize(); - // Write a file to create chunks - const data = Buffer.alloc(chunkSize * 2); - await fs.writeFile("/unique.txt", data); - - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - const result = (await lookupStmt.get(1, "unique.txt")) as { ino: number }; - const ino = result.ino; - - // Try to insert a duplicate chunk - should fail due to PRIMARY KEY constraint - const insertStmt = db.prepare( - "INSERT INTO fs_data (ino, chunk_index, data) VALUES (?, 0, ?)" - ); - - await expect( - insertStmt.run(ino, Buffer.from([1, 2, 3])) - ).rejects.toThrow(); - }); - - it("should store chunks with correct ordering in database", async () => { - const chunkSize = fs.getChunkSize(); - // Create 5 chunks with identifiable data - const dataSize = chunkSize * 5; - const data = Buffer.alloc(dataSize); - for (let i = 0; i < dataSize; i++) { - data[i] = i % 256; - } - await fs.writeFile("/ordered.bin", data); - - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - const result = (await lookupStmt.get(1, "ordered.bin")) as { - ino: number; - }; - const ino = result.ino; - - // Query chunks in order - const queryStmt = db.prepare( - "SELECT chunk_index FROM fs_data WHERE ino = ? ORDER BY chunk_index" - ); - const rows = (await queryStmt.all(ino)) as { chunk_index: number }[]; - - const indices = rows.map((r) => r.chunk_index); - expect(indices).toEqual([0, 1, 2, 3, 4]); - }); - }); - - // ==================== Cleanup Tests ==================== - - describe("Cleanup Tests", () => { - it("should delete all chunks when file is removed", async () => { - const chunkSize = fs.getChunkSize(); - // Create multi-chunk file - const data = Buffer.alloc(chunkSize * 4); - await fs.writeFile("/deleteme.txt", data); - - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - const result = (await lookupStmt.get(1, "deleteme.txt")) as { - ino: number; - }; - const ino = result.ino; - - const countStmt = db.prepare( - "SELECT COUNT(*) as count FROM fs_data WHERE ino = ?" - ); - const beforeResult = (await countStmt.get(ino)) as { count: number }; - expect(beforeResult.count).toBe(4); - - // Delete the file - await fs.deleteFile("/deleteme.txt"); - - // Verify all chunks are gone - const afterResult = (await countStmt.get(ino)) as { count: number }; - expect(afterResult.count).toBe(0); - }); - - it("should handle multiple files with different sizes correctly", async () => { - const chunkSize = fs.getChunkSize(); - - // Create files of various sizes - const files: [string, number][] = [ - ["/tiny.txt", 10], - ["/small.txt", Math.floor(chunkSize / 2)], - ["/exact.txt", chunkSize], - ["/medium.txt", chunkSize * 2 + 100], - ["/large.txt", chunkSize * 5], - ]; - - for (const [path, size] of files) { - const data = Buffer.alloc(size); - for (let i = 0; i < size; i++) { - data[i] = i % 256; - } - await fs.writeFile(path, data); - } - - // Verify each file has correct data and chunk count - for (const [path, size] of files) { - const readData = (await fs.readFile(path)) as Buffer; - expect(readData.length).toBe(size); - - const expectedChunks = size === 0 ? 0 : Math.ceil(size / chunkSize); - - const name = path.split("/").pop()!; - const lookupStmt = db.prepare(` - SELECT d.ino FROM fs_dentry d WHERE d.parent_ino = ? AND d.name = ? - `); - const result = (await lookupStmt.get(1, name)) as { ino: number }; - const countStmt = db.prepare( - "SELECT COUNT(*) as count FROM fs_data WHERE ino = ?" - ); - const countResult = (await countStmt.get(result.ino)) as { - count: number; - }; - - expect(countResult.count).toBe(expectedChunks); - } - }); - }); -}); diff --git a/sdk/typescript/tests_browser/index.test.ts b/sdk/typescript/tests_browser/index.test.ts deleted file mode 100644 index df82c6d5..00000000 --- a/sdk/typescript/tests_browser/index.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { Database } from "@tursodatabase/database-wasm/vite"; -import { AgentFS } from "../"; -import { Buffer } from "buffer"; - -describe("AgentFS Integration Tests", () => { - let agent: AgentFS; - beforeEach(async () => { - const db = new Database(":memory:"); - await db.connect(); - agent = await AgentFS.openWith(db); - }); - - afterEach(async () => { - await agent.close(); - }); - - describe("Initialization", () => { - it("should successfully initialize with an id", async () => { - expect(agent).toBeDefined(); - expect(agent).toBeInstanceOf(AgentFS); - }); - it("should polyfill Buffer automatically", async () => { - const chunkSize = agent.fs.getChunkSize(); - // Create sequential bytes spanning multiple chunks - const dataSize = chunkSize * 5; - const data = Buffer.alloc(dataSize); - for (let i = 0; i < dataSize; i++) { - data[i] = i % 256; - } - await agent.fs.writeFile("/sequential.bin", data); - - const readData = (await agent.fs.readFile("/sequential.bin")) as Buffer; - - // Verify every byte is in the correct position - for (let i = 0; i < dataSize; i++) { - expect(readData[i]).toBe(i % 256); - } - }); - }); -}); diff --git a/sdk/typescript/tests_browser/kvstore.test.ts b/sdk/typescript/tests_browser/kvstore.test.ts deleted file mode 100644 index d665e783..00000000 --- a/sdk/typescript/tests_browser/kvstore.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { describe, it, expect, beforeEach } from "vitest"; -import { Database } from "@tursodatabase/database-wasm/vite"; -import { KvStore } from "../src/kvstore.js"; - -describe("KvStore Integration Tests", () => { - let db: Database; - let kvStore: KvStore; - let tempDir: string; - let dbPath: string; - - beforeEach(async () => { - // Create temporary directory for test database - db = new Database(":memory:"); - await db.connect(); - // sync uses CDC so we must ensure that AgentFS components works properly with this extra setup - await db.exec("PRAGMA unstable_capture_data_changes_conn('full')"); - kvStore = await KvStore.fromDatabase(db); - }); - - describe("Basic Operations", () => { - it("should set and get a string value", async () => { - await kvStore.set("test-key", "test-value"); - const value = await kvStore.get("test-key"); - expect(value).toBe("test-value"); - }); - - it("should set and get an object value", async () => { - const testObject = { name: "test", count: 42, nested: { value: true } }; - await kvStore.set("object-key", testObject); - const value = await kvStore.get("object-key"); - expect(value).toEqual(testObject); - }); - - it("should set and get a number value", async () => { - await kvStore.set("number-key", 12345); - const value = await kvStore.get("number-key"); - expect(value).toBe(12345); - }); - - it("should set and get a boolean value", async () => { - await kvStore.set("bool-key", true); - const value = await kvStore.get("bool-key"); - expect(value).toBe(true); - }); - - it("should set and get an array value", async () => { - const testArray = [1, 2, "three", { four: 4 }]; - await kvStore.set("array-key", testArray); - const value = await kvStore.get("array-key"); - expect(value).toEqual(testArray); - }); - it("should set and list values", async () => { - await kvStore.set("g1:k1", 1); - await kvStore.set("g1:k2", 2); - await kvStore.set("g2:k1", 3); - await kvStore.set("g2:k2", 4); - expect(await kvStore.list("g1:")).toEqual([ - { key: "g1:k1", value: 1 }, - { key: "g1:k2", value: 2 }, - ]); - expect(await kvStore.list("g1:k1")).toEqual([{ key: "g1:k1", value: 1 }]); - expect(await kvStore.list("g1:k3")).toEqual([]); - expect(await kvStore.list("g2:")).toEqual([ - { key: "g2:k1", value: 3 }, - { key: "g2:k2", value: 4 }, - ]); - }); - }); - - describe("Update Operations", () => { - it("should update an existing value", async () => { - await kvStore.set("update-key", "initial-value"); - await kvStore.set("update-key", "updated-value"); - const value = await kvStore.get("update-key"); - expect(value).toBe("updated-value"); - }); - - it("should update value type", async () => { - await kvStore.set("type-key", "string-value"); - await kvStore.set("type-key", { object: "value" }); - const value = await kvStore.get("type-key"); - expect(value).toEqual({ object: "value" }); - }); - }); - - describe("Delete Operations", () => { - it("should delete an existing key", async () => { - await kvStore.set("delete-key", "value-to-delete"); - await kvStore.delete("delete-key"); - const value = await kvStore.get("delete-key"); - expect(value).toBeUndefined(); - }); - - it("should handle deleting non-existent key", async () => { - // Should not throw an error when deleting a non-existent key - await kvStore.delete("non-existent-key"); - // If we get here without throwing, the test passes - expect(true).toBe(true); - }); - }); - - describe("Edge Cases", () => { - it("should return undefined for non-existent key", async () => { - const value = await kvStore.get("non-existent-key"); - expect(value).toBeUndefined(); - }); - - it("should handle null values", async () => { - await kvStore.set("null-key", null); - const value = await kvStore.get("null-key"); - expect(value).toBeNull(); - }); - - it("should handle empty string", async () => { - await kvStore.set("empty-key", ""); - const value = await kvStore.get("empty-key"); - expect(value).toBe(""); - }); - - it("should handle zero value", async () => { - await kvStore.set("zero-key", 0); - const value = await kvStore.get("zero-key"); - expect(value).toBe(0); - }); - - it("should handle keys with special characters", async () => { - const specialKey = "key:with/special.chars@123"; - await kvStore.set(specialKey, "value"); - const value = await kvStore.get(specialKey); - expect(value).toBe("value"); - }); - }); - - describe("Concurrent Operations", () => { - it("should handle multiple concurrent sets", async () => { - const promises = Array.from({ length: 10 }, (_, i) => - kvStore.set(`concurrent-key-${i}`, `value-${i}`) - ); - await Promise.all(promises); - - // Verify all values were set - for (let i = 0; i < 10; i++) { - const value = await kvStore.get(`concurrent-key-${i}`); - expect(value).toBe(`value-${i}`); - } - }); - - it("should handle concurrent reads", async () => { - await kvStore.set("read-key", "read-value"); - - const promises = Array.from({ length: 10 }, () => - kvStore.get("read-key") - ); - const values = await Promise.all(promises); - - values.forEach((value) => { - expect(value).toBe("read-value"); - }); - }); - }); - - describe("Large Data", () => { - it("should handle large string values", async () => { - const largeString = "x".repeat(10000); - await kvStore.set("large-string", largeString); - const value = await kvStore.get("large-string"); - expect(value).toBe(largeString); - }); - - it("should handle large object values", async () => { - const largeObject = { - items: Array.from({ length: 1000 }, (_, i) => ({ - id: i, - name: `Item ${i}`, - data: `Data for item ${i}`, - })), - }; - await kvStore.set("large-object", largeObject); - const value = await kvStore.get("large-object"); - expect(value).toEqual(largeObject); - }); - }); - - describe("Persistence", () => { - it("should persist data across KvStore instances", async () => { - await kvStore.set("persist-key", "persist-value"); - - // Create new KvStore instance with same database - const newKvStore = await KvStore.fromDatabase(db); - const value = await newKvStore.get("persist-key"); - expect(value).toBe("persist-value"); - }); - }); -}); diff --git a/sdk/typescript/tests_browser/toolcalls.test.ts b/sdk/typescript/tests_browser/toolcalls.test.ts deleted file mode 100644 index 7fe84a70..00000000 --- a/sdk/typescript/tests_browser/toolcalls.test.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { describe, it, expect, beforeEach } from "vitest"; -import { Database } from "@tursodatabase/database-wasm/vite"; -import { ToolCalls } from "../src/toolcalls.js"; - -describe("ToolCalls Integration Tests", () => { - let db: Database; - let toolCalls: ToolCalls; - - beforeEach(async () => { - db = new Database(":memory:"); - await db.connect(); - // sync uses CDC so we must ensure that AgentFS components works properly with this extra setup - await db.exec("PRAGMA unstable_capture_data_changes_conn('full')"); - toolCalls = await ToolCalls.fromDatabase(db); - }); - - describe("Basic Operations", () => { - it("should start a tool call and return an ID", async () => { - const id = await toolCalls.start("test_tool", { arg1: "value1" }); - expect(id).toBeGreaterThan(0); - }); - - it("should start a tool call without parameters", async () => { - const id = await toolCalls.start("simple_tool"); - expect(id).toBeGreaterThan(0); - - const toolCall = await toolCalls.get(id); - expect(toolCall).toBeDefined(); - expect(toolCall?.name).toBe("simple_tool"); - expect(toolCall?.parameters).toBeUndefined(); - expect(toolCall?.status).toBe("pending"); - }); - - it("should mark a tool call as successful", async () => { - const id = await toolCalls.start("test_tool", { input: "test" }); - await toolCalls.success(id, { output: "result" }); - - const toolCall = await toolCalls.get(id); - expect(toolCall).toBeDefined(); - expect(toolCall?.status).toBe("success"); - expect(toolCall?.result).toEqual({ output: "result" }); - expect(toolCall?.completed_at).toBeGreaterThan(0); - expect(toolCall?.duration_ms).toBeGreaterThanOrEqual(0); - }); - - it("should mark a tool call as successful without result", async () => { - const id = await toolCalls.start("test_tool", { input: "test" }); - await toolCalls.success(id); - - const toolCall = await toolCalls.get(id); - expect(toolCall).toBeDefined(); - expect(toolCall?.status).toBe("success"); - expect(toolCall?.result).toBeUndefined(); - }); - - it("should mark a tool call as failed", async () => { - const id = await toolCalls.start("test_tool", { input: "test" }); - await toolCalls.error(id, "Something went wrong"); - - const toolCall = await toolCalls.get(id); - expect(toolCall).toBeDefined(); - expect(toolCall?.status).toBe("error"); - expect(toolCall?.error).toBe("Something went wrong"); - expect(toolCall?.completed_at).toBeGreaterThan(0); - expect(toolCall?.duration_ms).toBeGreaterThanOrEqual(0); - }); - - it("should get a tool call by ID", async () => { - const id = await toolCalls.start("test_tool", { arg: "value" }); - const toolCall = await toolCalls.get(id); - - expect(toolCall).toBeDefined(); - expect(toolCall?.id).toBe(id); - expect(toolCall?.name).toBe("test_tool"); - expect(toolCall?.parameters).toEqual({ arg: "value" }); - expect(toolCall?.status).toBe("pending"); - expect(toolCall?.started_at).toBeGreaterThan(0); - }); - - it("should return undefined for non-existent ID", async () => { - const toolCall = await toolCalls.get(99999); - expect(toolCall).toBeUndefined(); - }); - }); - - describe("Query Operations", () => { - it("should get tool calls by name", async () => { - await toolCalls.start("tool_a", { test: 1 }); - await toolCalls.start("tool_b", { test: 2 }); - await toolCalls.start("tool_a", { test: 3 }); - - const results = await toolCalls.getByName("tool_a"); - expect(results).toHaveLength(2); - expect(results.every((tc) => tc.name === "tool_a")).toBe(true); - }); - - it("should limit results when querying by name", async () => { - await toolCalls.start("tool_a", { test: 1 }); - await toolCalls.start("tool_a", { test: 2 }); - await toolCalls.start("tool_a", { test: 3 }); - - const results = await toolCalls.getByName("tool_a", 2); - expect(results).toHaveLength(2); - }); - - it("should get recent tool calls", async () => { - await toolCalls.start("tool_old"); - // Wait a full second to ensure different timestamps - await new Promise((resolve) => setTimeout(resolve, 1100)); - const midpoint = Math.floor(Date.now() / 1000); - // Wait a bit more to ensure next call is definitely after midpoint - await new Promise((resolve) => setTimeout(resolve, 1100)); - await toolCalls.start("tool_new"); - - const results = await toolCalls.getRecent(midpoint); - expect(results.length).toBeGreaterThanOrEqual(1); - expect(results.every((tc) => tc.started_at >= midpoint)).toBe(true); - }); - - it("should limit recent tool calls", async () => { - const now = Math.floor(Date.now() / 1000); - - await toolCalls.start("tool_1"); - await toolCalls.start("tool_2"); - await toolCalls.start("tool_3"); - - const results = await toolCalls.getRecent(now - 10, 2); - expect(results.length).toBeLessThanOrEqual(2); - }); - - it("should return empty array when no matching tool calls by name", async () => { - const results = await toolCalls.getByName("non_existent_tool"); - expect(results).toEqual([]); - }); - }); - - describe("Statistics", () => { - it("should calculate tool call statistics", async () => { - // Create some tool calls - const id1 = await toolCalls.start("tool_a", { test: 1 }); - await toolCalls.success(id1, { result: "ok" }); - - const id2 = await toolCalls.start("tool_a", { test: 2 }); - await toolCalls.error(id2, "failed"); - - const id3 = await toolCalls.start("tool_a", { test: 3 }); - await toolCalls.success(id3, { result: "ok" }); - - const id4 = await toolCalls.start("tool_b", { test: 4 }); - await toolCalls.success(id4, { result: "ok" }); - - const stats = await toolCalls.getStats(); - - expect(stats).toHaveLength(2); - - const toolAStats = stats.find((s) => s.name === "tool_a"); - expect(toolAStats).toBeDefined(); - expect(toolAStats?.total_calls).toBe(3); - expect(toolAStats?.successful).toBe(2); - expect(toolAStats?.failed).toBe(1); - expect(toolAStats?.avg_duration_ms).toBeGreaterThanOrEqual(0); - - const toolBStats = stats.find((s) => s.name === "tool_b"); - expect(toolBStats).toBeDefined(); - expect(toolBStats?.total_calls).toBe(1); - expect(toolBStats?.successful).toBe(1); - expect(toolBStats?.failed).toBe(0); - }); - - it("should exclude pending calls from statistics", async () => { - const id1 = await toolCalls.start("tool_a", { test: 1 }); - await toolCalls.success(id1, { result: "ok" }); - - // This one stays pending - await toolCalls.start("tool_a", { test: 2 }); - - const stats = await toolCalls.getStats(); - const toolAStats = stats.find((s) => s.name === "tool_a"); - - expect(toolAStats).toBeDefined(); - expect(toolAStats?.total_calls).toBe(1); // Only completed calls - }); - - it("should return empty array when no completed calls", async () => { - await toolCalls.start("tool_a", { test: 1 }); - const stats = await toolCalls.getStats(); - expect(stats).toEqual([]); - }); - }); - - describe("Complex Parameters and Results", () => { - it("should handle complex nested parameters", async () => { - const complexParams = { - user: { id: 123, name: "Test User" }, - options: { timeout: 5000, retry: true }, - data: [1, 2, 3, 4, 5], - }; - - const id = await toolCalls.start("complex_tool", complexParams); - const toolCall = await toolCalls.get(id); - - expect(toolCall?.parameters).toEqual(complexParams); - }); - - it("should handle complex nested results", async () => { - const complexResult = { - data: { - items: [ - { id: 1, value: "a" }, - { id: 2, value: "b" }, - ], - }, - metadata: { count: 2, hasMore: false }, - }; - - const id = await toolCalls.start("complex_tool"); - await toolCalls.success(id, complexResult); - const toolCall = await toolCalls.get(id); - - expect(toolCall?.result).toEqual(complexResult); - }); - - it("should handle large parameters", async () => { - const largeParams = { - items: Array.from({ length: 100 }, (_, i) => ({ - id: i, - data: `Data for item ${i}`, - })), - }; - - const id = await toolCalls.start("large_tool", largeParams); - const toolCall = await toolCalls.get(id); - - expect(toolCall?.parameters).toEqual(largeParams); - }); - }); - - describe("Concurrent Operations", () => { - it("should handle multiple concurrent tool calls", async () => { - const promises = Array.from({ length: 10 }, (_, i) => - toolCalls.start(`tool_${i}`, { index: i }) - ); - const ids = await Promise.all(promises); - - expect(ids).toHaveLength(10); - expect(new Set(ids).size).toBe(10); // All IDs should be unique - }); - - it("should handle concurrent completions", async () => { - const ids = await Promise.all([ - toolCalls.start("tool_1"), - toolCalls.start("tool_2"), - toolCalls.start("tool_3"), - ]); - - await Promise.all([ - toolCalls.success(ids[0]), - toolCalls.error(ids[1], "error"), - toolCalls.success(ids[2]), - ]); - - const results = await Promise.all(ids.map((id) => toolCalls.get(id))); - - expect(results[0]?.status).toBe("success"); - expect(results[1]?.status).toBe("error"); - expect(results[2]?.status).toBe("success"); - }); - }); - - describe("Persistence", () => { - it("should persist tool calls across instances", async () => { - const id = await toolCalls.start("persist_tool", { test: "value" }); - await toolCalls.success(id, { result: "ok" }); - - // Create new ToolCalls instance with same database - const newToolCalls = await ToolCalls.fromDatabase(db); - const toolCall = await newToolCalls.get(id); - - expect(toolCall).toBeDefined(); - expect(toolCall?.name).toBe("persist_tool"); - expect(toolCall?.status).toBe("success"); - }); - }); - - describe("Ordering", () => { - it("should return tool calls ordered by started_at desc", async () => { - await toolCalls.start("tool_first"); - await new Promise((resolve) => setTimeout(resolve, 100)); - await toolCalls.start("tool_second"); - await new Promise((resolve) => setTimeout(resolve, 100)); - await toolCalls.start("tool_third"); - - const recent = await toolCalls.getRecent(0); - - expect(recent.length).toBeGreaterThanOrEqual(3); - // Most recent first - for (let i = 0; i < recent.length - 1; i++) { - expect(recent[i].started_at).toBeGreaterThanOrEqual( - recent[i + 1].started_at - ); - } - }); - }); -}); diff --git a/sdk/typescript/tsconfig.json b/sdk/typescript/tsconfig.json deleted file mode 100644 index e6a50be3..00000000 --- a/sdk/typescript/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "module": "nodenext", - "target": "esnext", - "moduleResolution": "nodenext", - "lib": [ - "ES2020" - ], - "declaration": true, - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true - }, - "include": [ - "src/**/*" - ], - "exclude": [ - "node_modules", - "dist", - "examples" - ] -} \ No newline at end of file diff --git a/sdk/typescript/vitest.browser.config.ts b/sdk/typescript/vitest.browser.config.ts deleted file mode 100644 index 5d038faf..00000000 --- a/sdk/typescript/vitest.browser.config.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { defineConfig } from 'vitest/config'; -import { playwright } from "@vitest/browser-playwright" - -export default defineConfig({ - server: { - headers: { - "Cross-Origin-Embedder-Policy": "require-corp", - "Cross-Origin-Opener-Policy": "same-origin" - }, - }, - test: { - include: ['tests_browser/**/*.test.ts'], - browser: { - enabled: true, - provider: playwright(), - instances: [ - { browser: 'chromium' }, - { browser: 'firefox' } - ], - }, - }, -}); \ No newline at end of file diff --git a/sdk/typescript/vitest.config.ts b/sdk/typescript/vitest.config.ts deleted file mode 100644 index fe8286de..00000000 --- a/sdk/typescript/vitest.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - environment: 'node', - include: ['tests/**/*.test.ts', 'src/integrations/**/*.test.ts'], - exclude: ['**/node_modules/**', '**/dist/**', '**/resources/**'], - coverage: { - provider: 'v8', - reporter: ['text', 'json', 'html'], - exclude: ['node_modules/', 'dist/', 'tests/', 'resources/'], - }, - }, -});